Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2.x' into 2.x
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
  • Loading branch information
matdave committed Aug 12, 2022
2 parents 8968a17 + e4a78f3 commit 8f8691c
Show file tree
Hide file tree
Showing 49 changed files with 2,549 additions and 28 deletions.
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
74 changes: 71 additions & 3 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 @@ -108,3 +112,67 @@ Phone: [[!+twilio.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

0 comments on commit 8f8691c

Please sign in to comment.