Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Totp #1

Merged
merged 8 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Twilio 1.2.0
===============
- Add Time-based One Time Password (TOTP)
- Updated examples

Twilio 1.1.0
===============
- Add TwilioValidatePhone snippet to validate phone number
Expand Down
76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Twilio
# Twilio Verify

## Sample Registration page
Twilio is a verification service that allows you to send a code to a user's phone or use a time-based one time password to verify their identity. You can find more information on Twilio at [https://www.twilio.com/](https://www.twilio.com/). This is not a free service, so you will need to sign up for a Twilio account. Each successful verification will cost you $0.05, so this is something to consider before implementing.

## Phone Verification

### Sample Registration page

```html
<link
Expand Down Expand Up @@ -66,7 +70,7 @@
</script>
```

## Sample Activation Page (&twilioActivationResourceId)
### Sample Activation Page (&twilioActivationResourceId)

```html
[[!TwilioGetPhone]]
Expand Down Expand Up @@ -107,4 +111,68 @@ Phone: [[!+twilio.phone]]
<input type="submit" name="verify" value="Validate my phone" />
</div>
</form>
```
```

## Time-based One Time Password

### Sample Challenge Page

Create a challenge page and set the system setting `twilio.totp_challenge_page` to the page ID.

```html
[[!FormIt?
&hooks=`TwilioTOTPChallenge,TwilioVerify`
&twilioRedirect=`4` // ID of the page to redirect to after verification
&twilioFactorType=`totp`
&validate=`code:required`
]]
<form method="post">
<label>
Enter 2FA Code
<input name="code" value="" />
</label>
<button type="submit">Submit</button>
</form>
```

### Sample Create/Reset Token Page

Create a page with the following content:

```html
[[TwilioTOTPCreate?twilioRedirect=`4`]]
```

### Sample Profile Page

```html
[[!TwilioTOTPqr]]

[[!+twilio.qr:ne=``:then=`
<img src="[[!+twilio.qr]]" />
<p>Secret [[!+twilio.secret]]</p>
[[!+twilio.status:is=`unverified`:then=`
<p><a href="[[~5]]"><strong>Verify 2FA Code Before Next Login</strong></a></p> <!-- link to challenge page -->
`:else=``]]
<p><a href="[[~6]]">Refresh 2FA</a><br /> <!-- link to create / refresh page -->
<a href="[[~6?status=`disable_totp`]]">Disable 2FA</a></p>
`:else=`
<a href="[[~6]]">Enable 2FA</a>
`]]
```

## System Settings

| key | description |
| --- |------------------------------------------------------------------------------------------------------------|
| twilio.account_sid | Twilio Account SID - Found under Account Info here https://console.twilio.com/ |
| twilio.account_token | Twilio Auth Token - Found under Account Info here https://console.twilio.com/ |
| twilio.service_id | Twilio Service ID - Found under Services Page here https://console.twilio.com/us1/develop/verify/services |
| twilio.totp_enforce | Enforce 2FA for all users |
| twilio.totp_email_on_login | Email a code to the user when they login |
| twilio.totp_challenge_page | Page ID of the challenge page |

## Manager Page

Twilio 2FA Verification can be enabled in the manager login as well. You can view the status of Twilio 2FA for each user in the menu under
"Extras -> User Authentication"
53 changes: 52 additions & 1 deletion _build/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
"lowCaseName": "twilio",
"description": "Twilio for MODX Revolution 2.x",
"author": "John Peca",
"version": "1.1.0",
"version": "1.2.0",
"package": {
"menus": [
{
"text": "twilio.users",
"description": "twilio.users.desc",
"action": "users",
"permissions": "twilio_manage_auth"
}
],
"elements": {
"snippets": [
{
Expand All @@ -26,6 +34,28 @@
{
"name": "TwilioValidatePhone",
"file": "TwilioValidatePhone.php"
},
{
"name": "TwilioTOTPChallenge",
"file": "TwilioTOTPChallenge.php"
},
{
"name": "TwilioTOTPCreate",
"file": "TwilioTOTPCreate.php"
},
{
"name": "TwilioTOTPqr",
"file": "TwilioTOTPqr.php"
}
],
"plugins": [
{
"name": "Twilio",
"file": "Twilio.php",
"events": [
"OnBeforeManagerPageInit",
"OnManagerPageInit"
]
}
]
},
Expand All @@ -42,9 +72,30 @@
{
"key": "service_id",
"value": ""
},
{
"key": "totp_enforce",
"value": "0",
"type": "combo-boolean"
},
{
"key": "totp_email_on_login",
"value": "0",
"type": "combo-boolean"
},
{
"key": "totp_challenge_page",
"value": ""
}
]
},
"build" : {
"resolver" : {
"after" : [
"acls.php"
]
}
},
"dependencies": [
{
"name": "formit",
Expand Down
29 changes: 29 additions & 0 deletions _build/resolvers/acls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
if ($object->xpdo) {
switch ($options[xPDOTransport::PACKAGE_ACTION]) {
case xPDOTransport::ACTION_INSTALL:
case xPDOTransport::ACTION_UPGRADE:
/** @var modX $modx */
$modx =& $object->xpdo;

$permissions = array('twilio_manage_auth');

foreach ($permissions as $permission) {
$accessPermission = $modx->getObject('modAccessPermission', array(
'template' => 1,
'name' => $permission
));

if (!$accessPermission) {
$accessPermission = $modx->newObject('modAccessPermission');
$accessPermission->set('template', 1);
$accessPermission->set('name', $permission);
$accessPermission->set('value', 1);
$accessPermission->save();
}
}

break;
}
}
return true;
22 changes: 22 additions & 0 deletions assets/components/twilio/connector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
require_once dirname(dirname(dirname(dirname(__FILE__)))).'/config.core.php';
require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
require_once MODX_CONNECTORS_PATH . 'index.php';

$corePath = $modx->getOption('twilio.core_path', null, $modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/twilio/');
$twilio = $modx->getService(
'twilio',
'Twilio',
$corePath . 'model/twilio/',
array(
'core_path' => $corePath
)
);

/* handle request */
$modx->request->handleRequest(
array(
'processors_path' => $twilio->getOption('processorsPath', null, $corePath . 'processors/'),
'location' => '',
)
);
3 changes: 3 additions & 0 deletions assets/components/twilio/css/2fa.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#modx-leftbar, .x-layout-cmini-west {
display: none !important;
}
14 changes: 14 additions & 0 deletions assets/components/twilio/js/mgr/helpers/qr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Ext.onReady(function() {
var div = document.getElementById('modx-panel-profile-update');
div.innerHTML = div.innerHTML + '<div class="x-form-item x-tab-item x-form-element">'
+ '<label for="qrcode" style="width:auto;" class="x-form-item-label">'+
twilio.qrText
+':\n\</label></div>'
+ '<div id="qrcode"><img id="qrimg" src=""></div>';
MODx.Ajax.request({
url: twilio.config.connector_url,
params:{action:'totp/qr', user: twilio.config.user.user},
listeners:{
'success':{fn:function(r){ document.getElementById("qrimg").src = r.object.qr;},scope:this },
'failure':{fn:function(){ },scope:this}}});
});
12 changes: 12 additions & 0 deletions assets/components/twilio/js/mgr/sections/totp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
twilio.page.Totp = function(config) {
config = config || {};
Ext.applyIf(config,{
components: [{
xtype: 'twilio-panel-totp',
renderTo: 'twilio-panel-totp-div'
}]
});
twilio.page.Totp.superclass.constructor.call(this,config);
}
Ext.extend(twilio.page.Totp,MODx.Component);
Ext.reg('twilio-page-totp',twilio.page.Totp);
12 changes: 12 additions & 0 deletions assets/components/twilio/js/mgr/sections/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
twilio.page.Users = function(config) {
config = config || {};
Ext.applyIf(config,{
components: [{
xtype: 'twilio-panel-users',
renderTo: 'twilio-panel-users-div'
}]
});
twilio.page.Users.superclass.constructor.call(this,config);
}
Ext.extend(twilio.page.Users,MODx.Component);
Ext.reg('twilio-page-users',twilio.page.Users);
9 changes: 9 additions & 0 deletions assets/components/twilio/js/mgr/twilio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var Twilio = function (config) {
config = config || {};
Twilio.superclass.constructor.call(this,config);
};
Ext.extend(Twilio, Ext.Component, {
page:{},window:{},grid:{},tree:{},panel:{},combo:{},config: {}
});
Ext.reg('twilio',Twilio);
twilio = new Twilio();
89 changes: 89 additions & 0 deletions assets/components/twilio/js/mgr/widgets/totp.panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
twilio.panel.Totp = function (config) {
var twilioDeviceId = localStorage.getItem('twilio_device_id');
var generateId = function (length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
if (twilioDeviceId == null) {
twilioDeviceId = generateId(16);
localStorage.setItem('twilio_device_id', twilioDeviceId);
} else {
if (MODx.request.device_id == null) {
MODx.loadPage('totp', 'namespace=twilio&device_id=' + twilioDeviceId);
}
}
config = config || {};
Ext.applyIf(config,{
border: false
,baseCls: 'modx-formpanel'
,cls: 'container'
,items: [{
html: '<h2>' + _('twilio.2fa') + '</h2>'
,border: false
,cls: 'modx-page-header'
}, {
xtype: 'modx-formpanel',
layout: 'form',
id: 'twilio-form-totp',
cls: 'form-with-labels main-wrapper',
anchor: '100%',
border: false,
autoHeight: true,
style: 'padding-top: 15px',
url: twilio.config.connector_url,
waitMsg: _('twilio.verifying'),
success: function (form, action) {
console.log(action.result);
window.location = MODx.config.manager_url;
},
failure: function (form, action) {
Ext.Msg.alert(_('error'), action.result.message);
},
items: [{
html: '<p>' + (twilio.config.user.status === 'verified' ? _('twilio.2fa.challenge_msg') : _('twilio.2fa.verify_msg')) + '</p>'
},{
name: 'user',
xtype: 'hidden',
value: twilio.config.user.user
},{
name: 'action',
xtype: 'hidden',
value: twilio.config.user.status === 'verified' ? 'totp/challenge' : 'totp/verify'
},{
name: 'devicecode',
xtype: 'hidden',
value: twilioDeviceId
},{
name: 'code',
fieldLabel: _('twilio.2fa.code'),
xtype: 'textfield',
anchor: '100%',
},{
name: 'rememberdevice',
boxLabel: _('twilio.2fa.rememberdevice'),
xtype: 'checkbox',
anchor: '100%',
}],
buttons: [
{
text: _('twilio.submit'),
handler: function () {
var form = Ext.getCmp('twilio-form-totp');
if (form.isDirty()) {
form.submit();
}
}
}
]
}]
});
twilio.panel.Totp.superclass.constructor.call(this,config);
}
Ext.extend(twilio.panel.Totp, MODx.Panel);
Ext.reg('twilio-panel-totp',twilio.panel.Totp);
Loading