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 all 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
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
42 changes: 42 additions & 0 deletions sip-quickstart/.twilioserverlessrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"commands": {},
"environments": {},
"projects": {},
"assets": true /* Upload assets. Can be turned off with --no-assets */,
// "assetsFolder": null /* Specific folder name to be used for static assets */,
// "buildSid": null /* An existing Build SID to deploy to the new environment */,
// "createEnvironment": false /* Creates environment if it couldn't find it. */,
// "cwd": null /* Sets the directory of your existing Serverless project. Defaults to current directory */,
// "detailedLogs": false /* Toggles detailed request logging by showing request body and query params */,
// "edge": null /* Twilio API Region */,
// "env": null /* Path to .env file for environment variables that should be installed */,
// "environment": "dev" /* The environment name (domain suffix) you want to use for your deployment. Alternatively you can specify an environment SID starting with ZE. */,
// "extendedOutput": false /* Show an extended set of properties on the output */,
// "force": false /* Will run deployment in force mode. Can be dangerous. */,
// "forkProcess": true /* Disable forking function processes to emulate production environment */,
// "functionSid": null /* Specific Function SID to retrieve logs for */,
// "functions": true /* Upload functions. Can be turned off with --no-functions */,
// "functionsFolder": null /* Specific folder name to be used for static functions */,
// "inspect": null /* Enables Node.js debugging protocol */,
// "inspectBrk": null /* Enables Node.js debugging protocol, stops execution until debugger is attached */,
// "legacyMode": false /* Enables legacy mode, it will prefix your asset paths with /assets */,
// "live": true /* Always serve from the current functions (no caching) */,
// "loadLocalEnv": false /* Includes the local environment variables */,
// "loadSystemEnv": false /* Uses system environment variables as fallback for variables specified in your .env file. Needs to be used with --env explicitly specified. */,
// "logCacheSize": null /* Tailing the log endpoint will cache previously seen entries to avoid duplicates. The cache is topped at a maximum of 1000 by default. This option can change that. */,
// "logLevel": "info" /* Level of logging messages. */,
// "logs": true /* Toggles request logging */,
// "ngrok": null /* Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account). */,
// "outputFormat": "" /* Output the results in a different format */,
// "overrideExistingProject": false /* Deploys Serverless project to existing service if a naming conflict has been found. */,
// "port": "3000" /* Override default port of 3000 */,
// "production": false /* Promote build to the production environment (no domain suffix). Overrides environment flag */,
// "properties": null /* Specify the output properties you want to see. Works best on single types */,
// "region": null /* Twilio API Region */,
"runtime": "node14" /* The version of Node.js to deploy the build to. (node14) */,
// "serviceName": null /* Overrides the name of the Serverless project. Default: the name field in your package.json */,
// "serviceSid": null /* SID of the Twilio Serverless Service to deploy to */,
// "sourceEnvironment": null /* SID or suffix of an existing environment you want to deploy from. */,
// "tail": false /* Continuously stream the logs */,
// "template": null /* undefined */,
}
41 changes: 19 additions & 22 deletions sip-quickstart/assets/admin/admin-client.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
class AdminClient {
constructor() {
this.isReady = this.token !== null;
}

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 +25,15 @@ class AdminClient {
return response.json();
}

async checkAdminPassword() {
const response = await fetch('./check-admin-password');
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;
return this.token !== null;
}

Expand All @@ -52,11 +46,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
51 changes: 37 additions & 14 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="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,10 +23,11 @@ <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">
<div class="quickstart" v-if="adminClient.token">
<h1>Quickstart</h1>
<div class="initialization" v-if="!environment.valid">
<h2>{{ environment.title }}</h2>
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
58 changes: 58 additions & 0 deletions sip-quickstart/assets/admin/shared.private.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async function getCurrentEnvironment(context) {
return;
}
const client = context.getTwilioClient();

const services = await client.serverless.services.list();
for (const service of services) {
const environments = await client.serverless
Expand All @@ -53,6 +54,59 @@ async function getCurrentEnvironment(context) {
}
}

function adminPasswordChangedFromDefault() {
return process.env.ADMIN_PASSWORD !== 'default';
}

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

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 @@ -125,8 +179,12 @@ function urlForSiblingPage(newPage, ...paths) {
}

module.exports = {
adminPasswordChangedFromDefault,
adminPasswordMeetsComplexityRequirements,
checkAuthorization,
checkAdminPassword,
createToken,
generateEnvVariableInstructions,
getCurrentEnvironment,
getEnvironmentVariables,
getEnvironmentVariable,
Expand Down
Loading