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

Fix SIP Quickstart login issues #330

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c054468
Force user to update password before logging into Admin page
bld010 Feb 5, 2022
836fbd0
Linting
bld010 Feb 5, 2022
fdd17a5
Linting
bld010 Feb 5, 2022
1523d12
Merge branch 'twilio-labs:main' into main
bld010 Feb 15, 2022
03452fa
Merge branch 'twilio-labs:main' into DEVED-6524
bld010 Feb 15, 2022
ed47c84
Merge branch 'DEVED-6524' of https://github.com/bld010/function-templ…
bld010 Feb 15, 2022
e1759d0
Update .env to reflect password requirements
bld010 Feb 15, 2022
8a5537e
Merge branch 'twilio-labs:main' into DEVED-6524
bld010 Feb 23, 2022
812f095
Add logi
bld010 Feb 23, 2022
29862c9
Linting
bld010 Feb 23, 2022
f85a5c0
Linting
bld010 Feb 23, 2022
7e3450d
Merge branch 'DEVED-6524' of https://github.com/bld010/function-templ…
bld010 Feb 23, 2022
a7a00df
Add environment check for Functions UI, modify intial error handling …
bld010 Apr 12, 2022
dc87203
Rework how admin password messaging is generated and displayed
bld010 Apr 13, 2022
558b2e1
Linting
bld010 Apr 13, 2022
39d3309
Linting
bld010 Apr 13, 2022
028eadf
Linting
bld010 Apr 13, 2022
7720b67
Remove unnecessary vue data property
bld010 Apr 13, 2022
ffea26e
Fix typo
bld010 Apr 13, 2022
45f6d09
Change back to default password in env file :facepalm
bld010 Apr 13, 2022
67b8aba
Simplify regex method
bld010 May 3, 2022
fcfba94
Update name of check-admin-password function for consistency
bld010 May 3, 2022
e6df4ea
Simplify adminPasswordChangedFromDefault method
bld010 May 3, 2022
2af7e0a
Merge branch 'main' of https://github.com/twilio-labs/function-templa…
bld010 May 3, 2022
6ac9569
Update config files
bld010 May 3, 2022
d80c365
Remove isReady from Vue Template and AdminClient
bld010 May 3, 2022
9be5fcc
Add tests for new functions in shared.private.js
bld010 May 3, 2022
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
2 changes: 1 addition & 1 deletion sip-quickstart/.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# required: true
APP_NAME=SIP Quickstart

# description: Set a password for your app. Users who want to use the admin interface will have to use this password to access it
# description: Set a password for your app. (Must be at least 12 characters, contain at least 1 digit, and have mixed case.) Users who want to use the admin interface will have to use this password to access it
# format: text
# required: true
ADMIN_PASSWORD=default
Expand Down
38 changes: 20 additions & 18 deletions sip-quickstart/assets/admin/admin-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ class AdminClient {
}

async _handleResponse(response) {
if (!response.ok) {
if (response.status === 403) {
console.warn('Invalid token, resetting client');
this.token = null;
this.isReady = false;
}
if (response.ok === false) {
this.token = null;
this.isRead = false;
// Throw an error
// eslint-disable-next-line no-throw-literal
throw {
Expand All @@ -32,14 +29,16 @@ class AdminClient {
return response.json();
}

async checkAdminPassword() {
const response = await fetch('./check-adminPassword');
await this._handleResponse(response);
return true;
}

async login(password) {
try {
const result = await this._post('./login', { password });
this.token = result.token;
this.isReady = true;
} catch (err) {
console.error(`${err.statusCode}: ${err.message}`);
}
const result = await this._post('./login', { password });
this.token = result.token;
this.isReady = true;
return this.token !== null;
}

Expand All @@ -52,11 +51,14 @@ class AdminClient {
}

async fetchState() {
const response = await fetch(
`./check-status?token=${encodeURIComponent(this.token)}`
);
await this._handleResponse(response);
return response.json();
if (this.token) {
const response = await fetch(
`./check-status?token=${encodeURIComponent(this.token)}`
);
await this._handleResponse(response);
return response.json();
}
return false;
}

get token() {
Expand Down
49 changes: 36 additions & 13 deletions sip-quickstart/assets/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
</head>
<body>
<div id="app">
<div class="login" v-if="!adminClient.isReady">
<div v-if="!passwordChangedSuccessfully && errorMessage">
<h1>Change your admin password</h1>
<div v-html="errorMessage"></div>
<p>
The password must be a minimum of 12 characters, contain at least 1 digit, and have mixed case.
</p>
</div>
<div class="login" v-if="!adminClient.isReady && passwordChangedSuccessfully">
<h1>Password required</h1>
<p>
A password is required to update your environment. This can be found in your environment variables.
Expand All @@ -16,7 +23,8 @@ <h1>Password required</h1>
<input type="password" name="password" v-model="password">
</label>
</div>
<button>Let me in</button>
<button :disabled="!password || !passwordChangedSuccessfully">Let me in</button>
<p>{{ errorMessage }}</p>
</form>
</div>
<div class="quickstart" v-if="adminClient.isReady">
Expand All @@ -33,17 +41,16 @@ <h2>{{ environment.title }}</h2>
</ul>
</div>
<!-- Initialized environment -->
<div class="checklist" v-else>
<div class="checklist" v-if="environment.valid && environment.description">
<h2>{{ environment.title }}</h2>
<div v-html="md.render(environment.description)"></div>
<h2>Configuration Checklist</h2>
<ul>
<ul v-if="statuses.length">
<li v-for="status in statuses">
<div
v-bind:class="status.valid ? 'valid': 'invalid'"
v-html="md.render(status.title)" class="title"></div>
<p v-if="status.description"

v-html="md.render(status.description)" ></p>
<ul v-if="status.actions" class="actions">
<li v-for="action in status.actions">
Expand All @@ -66,13 +73,17 @@ <h2>Configuration Checklist</h2>
adminClient: new AdminClient(),
statuses: [],
environment: {},
password: null
password: null,
errorMessage: null,
passwordChangedSuccessfully: false,
},
methods: {
async refreshStatus() {
const result = await this.adminClient.fetchState();
this.environment = result.environment;
this.statuses = result.statuses;
if (result) {
this.environment = result.environment;
this.statuses = result.statuses;
}
},
async performAction(action) {
const message = await this.adminClient.postAction(action);
Expand All @@ -82,13 +93,25 @@ <h2>Configuration Checklist</h2>
this.refreshStatus();
},
async login() {
await this.adminClient.login(this.password);
await this.refreshStatus();
this.$forceUpdate();
}
this.errorMessage = null;
try {
await this.adminClient.login(this.password);
await this.refreshStatus();
this.$forceUpdate();
} catch (err) {
this.errorMessage = `${err.statusCode}: ${err.message}`
}
},
},
created() {
async created() {
this.md = window.markdownit();

try {
await this.adminClient.checkAdminPassword();
this.passwordChangedSuccessfully = true;
} catch (err) {
this.errorMessage = err.message;
}
this.refreshStatus();
}
});
Expand Down
60 changes: 60 additions & 0 deletions sip-quickstart/assets/admin/shared.private.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,65 @@ async function getCurrentEnvironment(context) {
}
}

function adminPasswordChangedFromDefault() {
if (process.env.ADMIN_PASSWORD === 'default') {
return false;
}
return true;
}

function adminPasswordMeetsComplexityRequirements() {
const regex = new RegExp('^(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{12,}$');
if (regex.test(process.env.ADMIN_PASSWORD)) {
return true;
}
return false;
}

async function generateEnvVariableInstructions(context) {
const env = await getCurrentEnvironment(context);

if (env) {
const client = context.getTwilioClient();
const service = await client.serverless.services(env.serviceSid).fetch();
if (service.uiEditable) {
const consoleUrl = `https://console.twilio.com/service/functions/${env.serviceSid}/runtime-functions-editor?currentFrameUrl=%2Fconsole%2Ffunctions%2Feditor%2F${env.serviceSid}%2Fenvironment%2F${env.sid}%2Fconfig%2Fvariables`;

return `<p>You can change the admin password by editing the <code>ADMIN_PASSWORD</code> value on the <a href=${consoleUrl} target="_blank">Environment tab of your Functions editor</a>.</p>
<p>After updating environment variables, you must redeploy your application by pressing the <strong>Deploy All</strong> button in your Functions editor.</p>`;
}
}

return `<p>You can change the admin password by editing the <code>ADMIN_PASSWORD</code> value in the <code>.env</code> file in the root of this project.</p>
<p>After you have edited and saved your <code>.env</code> file, please redeploy with the following command:</p>
<code>twilio serverless:deploy</code>`;
}

async function checkAdminPassword(context, event, callback) {
const response = new Twilio.Response();
let responseBody;

if (adminPasswordChangedFromDefault() === false) {
response.setStatusCode(403);

responseBody = `<p>You must change your admin password.</p>`;
} else if (adminPasswordMeetsComplexityRequirements() === false) {
response.setStatusCode(403);

responseBody = `<p>Your admin password does not meet the complexity requirements.</p>`;
} else {
response.setStatusCode(200);
callback(null, response);
return true;
}

responseBody += await generateEnvVariableInstructions(context);

response.setBody(responseBody);
callback(null, response);
return false;
}

async function getEnvironmentVariables(context, environment) {
const client = context.getTwilioClient();
return client.serverless
Expand Down Expand Up @@ -126,6 +185,7 @@ function urlForSiblingPage(newPage, ...paths) {

module.exports = {
checkAuthorization,
checkAdminPassword,
createToken,
getCurrentEnvironment,
getEnvironmentVariables,
Expand Down
36 changes: 7 additions & 29 deletions sip-quickstart/assets/admin/statuses.private.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { stripIndents } = require('common-tags');

const assets = Runtime.getAssets();
const { getCurrentEnvironment, urlForSiblingPage } = require(assets[
'/admin/shared.js'
].path);
const {
getCurrentEnvironment,
urlForSiblingPage,
usesFunctionUi,
} = require(assets['/admin/shared.js'].path);
const extensions = require(assets['/extensions.js'].path);

async function checkEnvironmentInitialization(context) {
Expand All @@ -14,13 +16,14 @@ async function checkEnvironmentInitialization(context) {
};
if (!environment) {
status.description = stripIndents`
This application is **must be** deployed.
This application **must be** deployed.

To deploy this function, use the following command:

\`\`\`bash
twilio serverless:deploy
\`\`\`

After it has been deployed, revisit this page in your deployed application.
`;
// eslint-disable-next-line no-negated-condition
Expand Down Expand Up @@ -384,30 +387,6 @@ async function getSipDomainIsWiredUp(context) {
}
return status;
}
async function getDefaultPasswordChanged(context) {
const status = {
title: 'Default admin password has been changed',
valid: false,
};
if (process.env.ADMIN_PASSWORD === 'default') {
status.description = stripIndents`
Please take a moment to change your admin password from the provided default password.

You can do this by editing the \`ADMIN_PASSWORD\` value in the \`.env\` in the root of this project.

After you have saved that file, please redeploy.

\`\`\`bash
twilio serverless:deploy
\`\`\`
`;
} else {
status.valid = true;
status.description =
"You're all set. You can change this value in your `.env` file at anytime.";
}
return status;
}

module.exports = {
environment: checkEnvironmentInitialization,
Expand All @@ -417,6 +396,5 @@ module.exports = {
getCredentialListStatus,
getIncomingNumberStatus,
getCallerIdStatus,
getDefaultPasswordChanged,
],
};
14 changes: 14 additions & 0 deletions sip-quickstart/functions/admin/check-adminPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const assets = Runtime.getAssets();
const { checkAdminPassword } = require(assets['/admin/shared.js'].path);

// eslint-disable-next-line consistent-return
exports.handler = async function (context, event, callback) {
const resultOfAdminPasswordCheck = await checkAdminPassword(
context,
event,
callback
);
if (resultOfAdminPasswordCheck) {
return callback(null, resultOfAdminPasswordCheck);
}
};
26 changes: 0 additions & 26 deletions sip-quickstart/tests/admin/private/statuses.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,6 @@ describe('sip-quickstart/admin/private/statuses', () => {
expect(status.description).toContain(expectedUrl);
});

test('getDefaultPasswordChanged is invalid if the default is used', async () => {
// Arrange
process.env.ADMIN_PASSWORD = 'default';

// Act
const status = await statusFunctions.getDefaultPasswordChanged(CONTEXT);

// Assert
expect(status).toBeDefined();
expect(status.valid).toBeFalsy();
expect(status.description).toContain('`.env`');
});

/*
* getIncomingNumberStatus,
* getCallerIdStatus,
Expand Down Expand Up @@ -502,17 +489,4 @@ describe('sip-quickstart/admin/private/statuses', () => {
expect(status.actions[0].name).toBe('clearIncomingNumber');
expect(status.actions[0].title).toContain('Choose a different');
});

test('getDefaultPasswordChanged is valid with different password', async () => {
// Arrange
process.env.ADMIN_PASSWORD = 'ch@ng3d';

// Act
const status = await statusFunctions.getDefaultPasswordChanged(CONTEXT);

// Assert
expect(status).toBeDefined();
expect(status.valid).toBeTruthy();
expect(status.description).toContain('all set');
});
});