311-data icon indicating copy to clipboard operation
311-data copied to clipboard

Update contact form to use frontend-only services

Open traycn opened this issue 1 year ago • 4 comments

Dependency

  • [ ] Talk to Bonnie about Netlify or AWS Serverless function

Overview

We need to update the contact form to use front-end only services for 311-Data users to submit their concerns as a github issue. Users would then receive a confirmation email.

This issue is created from the previous issue - Spike: Fix contact form to use frontend-only service


Solution:

Add 2 events after the existing form is submitted. For reference to the data we are using, the values collected on the forms submit are below.

// Contact Form field values:
{
     FirstName,
     LastName,
     Email,
     Association (optional),
     Message
}

Event 1: Create a new Github Issue:

Create a Github Issue using Octokit/Core.js. Documentation for the Create an issue POST here.

  • Set contact form field values into the POST request body as follows:
// Octokit - POST body data model  (open to change)
{
    owner: 'hackforla',
    repo: '311-data',
    title: 'Contact Form - {FirstName} {LastName} {Association}',
    body: '{FirstName, ` `, LastName,` `, Association} <br>
             Email - {Email} <br>
             Message - {Message}
          ',
    assignees: [
      '[insert default assignee here]'
    ],
    milestone: [insert default milestone here],
    labels: [
      'bug'
    ],
    headers: {
      'X-GitHub-Api-Version': '2022-11-28'
    }
}

On success trigger to send an email. Refer to Event 2.

Event 2: Send an email using EmailJS

Documentation for the sendForm() function here.

Action Items

Loc: 311-data.components.contact.ContactForm.jsx

Event 1:

  • [x] Create a personal access token for HackForLA github account, docs here.

ContactForm.jsx

  • [x] Install octokit/core.js package here.
  • [ ] Create octokit instance w/access token (do not make this token public)
    • [ ] Write a octokit.request POST function with the previously mentioned code snippet Octokit - POST body data model.
    • [ ] Execute the octokit.request POST function on the contact form submit.

Event 2:

  • [ ] Create a EmailJS account using HackForLA, sign up here.
    • [ ] Create an email service.
    • [ ] Create an email template.
    • [ ] Gather the email service and email template ID.

ContactForm.jsx

  • [ ] Create the sendForm() function, doc here with the email service and template ID.
    • [ ] Define values of request body based on Contact Form field values and Email template values.
    • [ ] Execute the sendForm() function after receiving a 201 code response from Octokit.

Resources/Instructions

Octokit

Octokit/Core.js documentation Octokit/Core.js package Create a new Github Access Token Create a new issue - Octokit documentation

EmailJS

Sign-Up for EmailJS Send an email after Contact Form is submitted

traycn avatar Feb 14 '24 23:02 traycn

This ticket is ready to be picked up, thank you @traycn

ryanfchase avatar Feb 15 '24 03:02 ryanfchase

I'm going to jot a ChatGPT response that I generated when I asked about measures we can take to secure our access tokens. I think this is relevant for all tokens we use, considering we don't have a backend:

cc @Skydodle

Token Security Strategies

Certainly! Without a backend server, you have to rely on alternative methods to secure your access token. Here's how you can adapt the suggestions:

  • Environment Variables: Since you don't have a backend server to store environment variables, you can't directly use this approach. Instead, consider storing the access token securely in your frontend code. Use techniques like encryption or obfuscation to make it more difficult for users to extract the token from your code.
  • Token Scope: When generating the access token, ensure that you only grant it the permissions necessary for the specific actions it needs to perform. Avoid granting excessive permissions that could potentially be misused if the token is compromised.
  • Client-side Proxy: Implement a client-side proxy using serverless functions or cloud services. For example, you can use AWS Lambda or Azure Functions to create lightweight proxy functions that make requests to GitHub's API on behalf of your frontend application. These functions can securely handle the access token and enforce access control policies.
  • Request Validation: Even though you don't have a backend server, you can still implement some level of request validation on the client-side. Validate requests to ensure they meet certain criteria before making API calls. This can include checking for required parameters, sanitizing input, and verifying the integrity of the request.
  • Token Rotation: Although you can't rotate access tokens as easily without a backend server, you can manually regenerate and update the token in your frontend code when necessary. Make sure to follow best practices for securely storing and updating the token.
  • Token Expiry: Set an expiry time for the access token and handle token expiration gracefully in your frontend code. When the token expires, prompt the user to reauthenticate or automatically refresh the token if possible.

Regarding token swapping strategies when a token is suspected to be misused:

  • Monitoring and Logging: Implement client-side logging and monitoring to track API usage and detect any suspicious activity associated with the access token. Monitor network requests, error logs, and other relevant metrics to identify potential security threats.
  • Token Revocation: If you suspect that a token has been compromised, manually revoke the token through GitHub's interface. Update your frontend code to use a new token and consider implementing additional security measures to prevent similar incidents in the future.
  • Token Blacklisting: Maintain a blacklist of revoked or compromised tokens locally in your frontend code. Update your application to reject requests using blacklisted tokens and notify users of the security incident.
  • User Notifications: Notify users of any suspicious activity detected in their account or associated with their access token. Provide clear instructions on how to secure their account and mitigate potential risks.

By adapting these strategies to your frontend-only architecture, you can still enhance the security of your access tokens and protect your resources from unauthorized access.

ryanfchase avatar Apr 27 '24 23:04 ryanfchase

Hello, I've been thinking about a different approach using AWS Lambda that might be worth considering.

High Level Overview

  1. Front-End Setup: When contact form is submitted, it makes a POST request to the serverless function URL (which is the Lambda function below).
  2. Lambda Serverless Function:
    • The function receives the form data, proces it and prepare it for submission to Github.
    • Script to interact with Github API. Authenticate with our Github access token and post the new issue to our Github repo.

Detail on AWS Lambda Implementation:

  1. Lambda Function Setup:
    • No Need for writing up a server: Log into AWS account and create a new Lambda function with JavaScript, then write a simple JavaScript handler that tells Lambda how to execute when we receive the HTTP request from the contact form.
    • API Gateway: Set up an API Gateway that acts as the front door for requests to trigger the Lambda function. When the contact form make a POST request to a URL provided by the API Gateway, which then invokes the Lambda function. Setting up the API Gateway does not require any coding at all, it's simply choosing configurations via the AWS platform.
  2. Function Logic (the only coding part):
    • Handle Request: The Lambda function receives the form data as a JSON payload, processes it as needed (validation, sanitization, restructuring the data etc.), and then prepares it for GitHub.
    • Create Github Issue: The Lambda function uses the GitHub API to create an issue in our repository. It would authenticate with GitHub using an access token and use the GitHub API to post the new issue.
  3. Secure Token:
    • Use AWS Secrets Manager or Environment Variables in the Lambda configuration to store sensitive information like GitHub access tokens securely.
    • Configure the neccesary IAM roles and permission for access to Lambda function and its interactions.

PROS

  • Completely eliminate any security issues from our frontend codebase. The token is store in AWS only, and not even in any script we wrote in the Lambda function but store and managed by the AWS Secret Manager.
  • Very few lines of code. We only need to write logic to handle the POST request, and how we want the data to appear in Github issue (which is dictated by Github API so we only have to follow instructions).
  • There is no need to write any server connection setup code or database management code.
  • Cost effective. AWS Lambda's free tier includes 1 million request per month and 400,000 GB-seoncds of compute time per month, which is a bit of an overkill for a simple contact form. But in the long run after we launch, it's much cheaper and safer than free tiers offered by Netlify or Vercel.

CONS

  • A learning curve to learn how to use and navigate the AWS platform if zero exposure. However, unlike programming where there could be a million ways of implementing things, navigating the plaform is straightforward. There is usually only one answer for doing each specific thing on AWS, and it's well documented that you can just follow it step by step.

Final Thoughts

In my opinion, AWS Lambda would be a quick and simple setup as most steps are take care of by AWS automations. However, my opinion is definitely biased since I had some exposure with AWS. I'm happy to hear other options or dicuss any possible issues with this route.

Skydodle avatar May 03 '24 22:05 Skydodle

@ryanfchase

There is also one major issue regardless of how we end up implementing this. We need to generate an access token in order to post a Github issue from whatever implementation we chose. I checked around for organizational permissions, and I think only Bonnie and Chelsea can generate access tokens. This might be something we have to bring up to Bonnie to see what to do best.

Skydodle avatar May 03 '24 22:05 Skydodle

ETA: Sunday 6/16 Availability: M-Sun after 6pm

Skydodle avatar Jun 08 '24 01:06 Skydodle

@ryanfchase

For the Personal Access Token, I would suggest to set it as no expiration, with the following scopes:

Mandatory:

  • Entire repo scope: mandatory for octokit and for CRUD operations on issues in repo

Optional Add-Ons For Future Functionality Expansion

  • workflow: for triggering Github Actions workflows in response to form submission
  • admin:repo_hook: to manage webhooks, which can send real-time data to exteranl services when events (push, PRs, etc.) occured in the repo. No solid ideas yet, but could be useful later if we want to use EmailJS to send more than a confirmation email to users
  • notification: access to notification for events, also thought it could be useful later maybe.

Skydodle avatar Jun 08 '24 03:06 Skydodle

@Skydodle I need to have the admin team create a bot account for us in order to automate the opening of issues.

Here's my references to how the website team is utilizing a bot in a smiliar capacity:

In the meantime, please develop with your own access token.

  • don't add it to the repo
  • simply use it in your .env file, ensuring that it doesn't get checked in

ryanfchase avatar Jun 08 '24 17:06 ryanfchase

Assigning myself while I create the emailjs deliverables ETA 6-8-24 Availability: weekends before 7p

ryanfchase avatar Jun 08 '24 18:06 ryanfchase

The EmailJS service has been set up within the 311-DataDev vault in 1Password.

  • note: do not pass a message body, the template contains the relevant copy
  • service name: 311 Data Contact Page Reply Service
  • template name: 311 Data Contact Page Reply Template

I'm also opening a ticket on the PM team (and possibly UIUX team) to take a pass at the contents of the page: https://github.com/hackforla/311-data/issues/1754

ryanfchase avatar Jun 08 '24 19:06 ryanfchase

@Skydodle I know you're sick so take your time. When you can, please provide an update on this ticket and an ETA of when you believe it will be complete.

ryanfchase avatar Jun 13 '24 23:06 ryanfchase

ETA: Friday 6/21 EOD Avaialbility: Friday to Monday after 5pm

Skydodle avatar Jun 15 '24 03:06 Skydodle

I'm realizing that to fully complete this ticket, I think this will become dependent on one other ticket:

  • https://github.com/hackforla/311-data/issues/1769

I was thinking that maybe it would also be dependent on the email template we are designing... but I think if we provide the css to the emailJS template on our account, @Skydodle's code is agnostic to how the email's formatting and inner contents.

In any case, @Skydodle let me know when you hit a point where you are fully blocked by #1769 -- and I will move this ticket to Icebox and list its dependency.

ryanfchase avatar Jun 16 '24 05:06 ryanfchase

Leaving note regarding the personal access token required for the octokit service. Currently for local development purpose, I created my own Github PAT. The PM team will need to create a HFLA bot account with its own PAT to use for the app in production.

For your reference, the scopes I chose when creating the PAT were repo, workflow, and notification.

Skydodle avatar Jun 18 '24 02:06 Skydodle

Overview

Did some testing for security, and found that our idea of directly using Octokit from client to create Github issue without a server is highly insecure. Even if we stored the Github Personal Access Token (PAT) in Github Secrets for production, at build time any process.env reference will be replace with the actual API keys. These keys can be exposed to anyone who inspect the JS code running in the browser.

I inspected our live site with Chrome Dev Tools and search for our Mapbox API token, and indeed it was easily found. See screenshot below, last line contains Mapbox access token:

Screenshot

Screenshot 2024-06-19 at 8 13 07 AM

The Mapbox API key is for public usage on Mapbox, which doesnt have any effect on our Github if stolen. The Github PAT however, if exposed can pose a serious security threat to our organization.


Options

  • Option 1: Use the Netlify serverless function mentioned above. Basically write a Octokit script that contains the PAT and upload to Netlify, our PAT token would be securely store in Netlify. Our client will make request to Netlify on form submit.
  • Option 2: Use Google Forms as @ryanfchase suggested and it's also what the HFLA Website team does for the Win page. Similarly, on the Google Form use Google Apps Script to edit the script and include our GithubPAT on form submit, then our token would be stored in the Google Apps Script server.
Details on Google Apps Script Implementation

1. Create a Google Form

  • Create a Google Form with the necessary fields (e.g., title, description, etc.).

2. Open Google Apps Script

  • In the Google Form, click on the three dots (More) at the top right and select Script Editor.

3. Write the Google Apps Script

  • Here’s an example script that triggers when the form is submitted:
function onFormSubmit(e) {
  var title = e.values[1]; 
  var body = e.values[2];   
  
  var payload = {
    "title": title,
    "body": body
  };
  
  var options = {
    "method" : "post",
    "headers": {
      "Authorization": "token YOUR_GITHUB_PAT"
    },
    "payload": JSON.stringify(payload),
    "contentType": "application/json"
  };
  
  var response = UrlFetchApp.fetch("https://api.github.com/repos/YOUR_GITHUB_USERNAME/YOUR_REPO/issues", options);
  Logger.log(response.getContentText());
}

function createTrigger() {
  var form = FormApp.openById("YOUR_FORM_ID");
  ScriptApp.newTrigger("onFormSubmit")
           .forForm(form)
           .onFormSubmit()
           .create();
}
  • Note: Need a Google account for our team if not already have one, and need to ensure access to the Google Apps Script project is restricted to trusted users.

Considerations

@ryanfchase I understand you want to go with option 2 to keep the application "frontend only". In my opinion, both options does the same thing. They both require the client to send a request to an outside server:

  • In Option 1, the current contact form hits the Netlify service we created
  • In Option 2, the Google Form hits the Google Apps Scripts API.

Both options has the benefit of storing the PAT token securely outside of our client code base.

The major difference we need to consider is the frontend design we intend to present to the users, which we should open up for discussion with design team and maybe research team:

  • Option 1 will use the existing contact form we have on the Contact page
  • Option 2 will either:
    • Embed the Google Form on the Contact page OR
    • Some other design that integrate the Google Form, something maybe the design team can come up with
  • Team to discuss which form would be user-centric on design, usability, and styling consistence with the rest of the app, etc.

Skydodle avatar Jun 19 '24 17:06 Skydodle

Hi @Skydodle,

Please leave a comment with the following items:

  • updated ETA
  • progress from the last week (if applicable)
  • availability for communications during the week

ryanfchase avatar Jun 20 '24 02:06 ryanfchase

Updated ETA: Friday 6/28 Avaialbility: Friday to Monday after 5pm

Skydodle avatar Jun 20 '24 03:06 Skydodle

Closing this ticket as we are taking a different implementation from this issue's approach. The original proposal exposes the access token without a server to store it. This ticket is replaced with #1780.

Skydodle avatar Jun 24 '24 23:06 Skydodle

Modifying as Not Planned due to technical limitations

ryanfchase avatar Jun 24 '24 23:06 ryanfchase