Helping Seniors During the Covid-19 Crisis



In one week, we partnered with local authorities to build a volunteer call center app to help vulnerable populations during the current crisis.


On Wednesday, March 18, the Israeli Ministry of Finance and Welfare asked Wix to build a system to organize their outreach efforts to senior citizens and those in other at risk groups. Their volunteers would be calling to check on one million Israeli individuals: do they need food delivered; home assistance; any other form of help?


The very next day we assembled a team, and one week later the “Golden Guard” system was ready for testing. On Monday, March 30, it was available to users.


You can see the user facing site here (Hebrew): apply.israelvolunteer.org.il

And here is the call center app for volunteers (Hebrew): www.israelvolunteer.org.il

This is the story of how we built this web application using Corvid by Wix.

Corvid is the Wix development platform, offering a Node.js backend, database, enabling integrations and FE coding with an integrated visual builder. The result is a productive development platform, ideal for delivering web applications on a tight schedule. (The name Corvid has nothing to do with Covid-19. Corvid is a family of very smart birds including crows and ravens.)



The System


We considered two key questions before building the application: what problem does the system need to solve, and what is the minimum required for it to be functional? Given a tight schedule, focusing on key functionality is a major enabler to deliver such a system on time.

We worked intensively with the ministry office to design the web application main user flows, understanding the roles and responsibilities and determining how the system can support them.

There are three roles in the system:


  • Volunteer

  • Coordinator - A volunteer who also manages a group of volunteers

  • Super Coordinator - An official from a local municipal authority who is responsible for an area


And there are two main areas of the system:


  • Volunteer Application system - For volunteers to apply to help, and for the government to identify, check, and approve volunteers

  • Call Center system - For volunteers to conduct the calls


The Approval System Flows


First, a potential volunteer gets a link to the application system. To apply, they have to sign in using the government portal, and then fill out an application form specifying who they are, their availability, etc. Once they submit the form, it goes through a validation check to ensure there are no legal blockers to them becoming a volunteer.

At this point, Super Coordinators check the lists of potential volunteers and approve people based on official criteria. Once approved, a volunteer gets both an SMS and an email to register to the call center system via a one-time use URL and token.

Why both email and SMS? Some email ends up as spam if it’s not sent from a known email address (i.e., an address in the Contacts of the email receiver). Also, some devices, like iPhones, don’t highlight SMS alerts as high priority. In any case, it’s better to have redundancy so no volunteer misses the notification.



The Call Center System Flows


When a volunteer is approved, they get an SMS and email with a link to register for the call center system. After registration, they receive a list of 20 citizens who are potentially in need. The volunteer selects an individual and clicks the Call button. At this time, the call center creates a call between the volunteer’s phone and the citizen’s phone, without exposing the phone numbers to either party. The volunteer explains the purpose of the call, confirms they have reached the correct individual, and asks about their condition, including if they need food, medical assistance, or any other type of assistance.

The information and details of the call are entered in a call summary form, which is then logged in the database for later review or subsequent calls. Based on the information collected from the call center, the relevant data is exported to a food distribution operation, as well as medical assistance providers, if required.

The Coordinator and Super Coordinator roles can also use the system for reports and to manage volunteers.



Technical Challenges


When building this system, we encountered a few challenges:


  1. We had to identify volunteers using the Israeli government portal, in order to validate that the volunteer registering online is actually the same person listed on the government issued physical ID card.

  2. Because we are storing a large government-regulated dataset, we had to use the right database solution.

  3. Volunteers are calling lots of citizens, so we had to protect their privacy by not exposing phone numbers.


Let’s dig into the challenges in order.

Challenge #1: User Identification Through the Government Portal

The challenge is making sure the real-world person who is volunteering matches the persona that is logging in. We want to make sure we are preventing stolen identity and impersonation of potential volunteers and coordinators.


The technical challenge is integrating with the government portal using the SAML 2.0 protocol for identifying the volunteers and coordinators.

Using SAML in Corvid is as simple as installing the saml2-js NPM package, and getting all the needed credentials and secrets from the government portal.

The diagram below shows the process.



On the volunteer page, there are four buttons with links for applying to volunteer (apply for a call center volunteer, apply for coordinator, and application for volunteer and coordinators of food delivery). Clicking any of the buttons navigates the volunteer to a Corvid router.

A Corvid router is server code that reacts to an HTTP request and either selects which page to show or redirects to another page. In this case, the router creates a SAML login request using the saml2-js package, which creates a redirect URL with an encoded and signed param (the SAML request). Then the router returns instructions to the browser to redirect to the government portal using the returned URL. The code for the router is below:



backend/routers.js

1    import { redirect, ok, notFound, WixRouterSitemapEntry } from "wix-router"; 
2    import wixUsersBackend from 'wix-users-backend'; 
3    import { getSecret } from 'wix-secrets-backend'; 
4    import wixData from 'wix-data'; 
5    import saml2 from 'saml2-js'; 
6     
7    import { loginServiceUrl, metadataUrl, logoutUrl, assertEndpoint, spCert, 
8      govEncryptionCertificate, govSiginingCertificate, idp_options } 
9      from "..." 
10    
11   let privateKey; 
12   getSecret("saml_gov").then(secret => privateKey = secret); 
13    
14   export async function login_Router(request) { 
15     try { 
16       const isLoggedIn = wixUsersBackend.currentUser.loggedIn 
17       if (isLoggedIn) { 
18         return redirect("/logedinusersArea") 
19       } 
20       if (!validateRequest(request)) 
21         return notFound("Missing query params"); 
22       const params = request.url.substr(request.url.indexOf('?')); 
23    
24       const sp_options = { 
25         entity_id: "...", 
26         private_key: privateKey, 
27         certificate: spCert, 
28         assert_endpoint: assertEndpoint + params, 
29         force_authn: true, 
30         nameid_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", 
31         sign_get_request: true, 
32         allow_unencrypted_assertion: false 
33       } 
34    
35       let sp = new saml2.ServiceProvider(sp_options); 
36       let idp = new saml2.IdentityProvider(idp_options); 
37       const url = await new Promise((resolve, reject) => { 
38         sp.create_login_request_url(idp, {}, (err, login_url, request_id) => { 
39           if (err !== null) { return reject(err) } 
40           resolve(login_url) 
41         }) 
42       }); 
43       return redirect(url, "302")); 
44   } 
45   catch (e) {...} 
46   }

Once a user logs in, the government portal calls a pre-configured API in the volunteer registration system, which is implemented as an HTTP function (POST method). An HTTP function, similar to AWS Lambda or GCP Cloud Functions, is a JavaScript function that is exported as an API for the project.

The body of the POST operation includes a SAML Response, decoded using the saml2-js package, which extracts from the SAML Response the user ID and stores the ID in the database. The function then returns the instruction to the browser to redirect to a form page (the targetPage parameter).

The specific HTTP function is below.



backend/http-functions.js

1    import { response, ok, badRequest } from 'wix-http-functions'; 
2    import wixData from 'wix-data'; 
3    import { getSecret } from 'wix-secrets-backend'; 
4    import saml2 from 'saml2-js'; 
5    import { loginServiceUrl, metadataUrl, logoutUrl, assertEndpoint, spCert, 
6      govEncryptionCertificate, govSiginingCertificate, idp_options } 
7      from "..." 
8     
9     
10   let privateKey; 
11   getSecret("saml_gov").then(secret => privateKey = secret); 
12    
13   const dbOptions = {suppressAuth: true}; 
14   export async function post_assertPost(request) { 
15     try { 
16       if (!validateRequest(request)) { 
17         return badRequest({body: {"error": "Bad request"}, 
18           headers: {"Content-Type": "application/json"}}); 
19       } 
20       let params = request.url.substr(request.url.indexOf('?')); 
21       let reqBody = await request.body.text(); 
22       reqBody = reqBody.slice(13) 
23    
24       const sp_options = { 
25         entity_id: "...", 
26         private_key: privateKey, 
27         certificate: spCert, 
28         assert_endpoint: assertEndpoint, 
29         force_authn: true, 
30         nameid_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", 
31         sign_get_request: true, 
32         allow_unencrypted_assertion: false 
33       } 
34    
35       let sp = new saml2.ServiceProvider(sp_options); 
36       let idp = new saml2.IdentityProvider(idp_options); 
37    
38       const options = { 
39         allow_unencrypted_assertion: false, 
40         request_body: { SAMLResponse: decodeURIComponent(reqBody) } 
41       }; 
42       const collectionItemId = await new Promise((resolve, reject) => { 
43         sp.post_assert(idp, options, async function (err, saml_response) { 
44           if (err !== null) { 
45             console.error(err) 
46             return reject(err); 
47           } 
48           if (saml_response && saml_response.user && 
49             saml_response.user.attributes && 
50             saml_response.user.attributes.IDNum) { 
51             const userData = { 
52               "gov_auth_collection": saml_response.user.attributes.IDNum }; 
53             const dataItem = await wixData.insert("gov_auth_collection", 
54               userData, dbOptions); 
55             resolve(dataItem._id) 
56           } 
57           return reject("...") 
58         }); 
59       }); 
60       const targetPage = request.query.targetPage; 
61       return response({ 
62         status: 302, 
63         headers: { 'Location': "https://.../" + targetPage + params + 
64         "&id=" + collectionItemId } }); 
65     }); 
66   } 
67   Catch (e) {...} 
68   }

After this code, the user ID is in the database and the code in the targetPage can load the user ID based on the synthetic collectionItemId. The onReady function is called for each Corvid page before it is shown to the user. In the onReady function, we use a web module to query the database.


pages/application-form.js

1    import wixLocation from 'wix-location'; 
2    import {getID} from 'backend/tz.jsw'; 
3     
4    $w.onReady(async function () { 
5      const itemIdFromMzCollection = wixLocation.query.id; 
6      try { 
7        if (!itemIdFromMzCollection) { 
8          wixLocation.to('/home') 
9        } else { 
10         let volunteerId = await getID(itemIdFromMzCollection); 
11         init(volunteerId); 
12       } 
13     } catch (err) {... 
14     } 
15   });


Challenge #2: Scalable and Secure Data Storage

The Corvid database is scalable and secure, but due to the government regulations and tight schedule, we didn’t have time to get it approved. So we needed another trusted and approved database solution.

We used GCP Cloud SQL. Fortunately, we have a pre-existing integration with GCP Cloud SQL in the form of an external DB driver, an open source project on GitHub.


It only took a few hours to set up the project and have a compliant database.

Challenge #3: Making Anonymous Calls

To protect the privacy of citizens, it was required that a volunteer could not see the phone number of the person they are calling. So we wanted the volunteer to simply click a button in the web application that would initiate a call between his phone and the citizen’s. This requires an API, such as the Twilio API.

For that, we created a web module—a Node.js module that, for each exported function, automatically creates a proxy module for use in the browser, allowing client code in the browser to call backend code as regular async functions. The functions in the web module are then called when the volunteer clicks the call button to initiate a phone call.

Here’s the code for the function:


backend/make-calls.jsw

1    import { getSecret } from 'wix-secrets-backend'; 
2    import wixData from 'wix-data'; 
3    import wixUsersBackend from 'wix-users-backend'; 
4    const accountSid = '...'; 
5    const twilioNumber = '...'; 
6     
7    export async function createCall(agentId, seniorCitizenId, callTo) { 
8      const agentData = await wixUsersBackend.getUser(agentId); 
9      const agentNumber = agentData.phones[0]; 
10     const seniorCitizenData = await wixData.get('senior_citizens', seniorCitizenId); 
11     let seniorCitizenNumber = callTo? 
12       seniorCitizenData.items[0].phone: 
13       seniorCitizenData.items[0].mobilePhone; 
14     try { 
15       secret = await getSecret("twillioAuthToken"); 
16       client = require('twilio')(accountSid, secret); 
17    
18       return client.calls 
19         .create({ 
20           url: 'https://twimlets.com/forward?PhoneNumber=' + seniorCitizenNumber, 
21           to: agentNumber, 
22           from: twilioNumber 
23         }) 
24         .then(call => { 
25           return call.status 
26         }) 
27     } catch (err) { 
28       console.error('createCall err', err); 
29       return 'failed to create a call - ' + err.message 
30     } 
31   }

And on the client page, here’s the code to connect a button with the server function of making the call:


pages/make-calls.js

1    import { createCall } from 'backend/twilioProxy.jsw'; 
2    ... 
3    $w('#callToMobile').onClick(() => { 
4      createCall(currentUserId, seniorCtznID, 'mobile'); 
5      $w('#callFeedback').show(); 
6      setTimeout(function () { $w('#callFeedback').hide(); }, 10000); 
7    })

Summary


Here at Wix we are proud to assist with the Covid-19 situation. We are using our platforms (Wix and Corvid), developers, designers, and product people to build multiple systems helping with different challenges.

In the same way we developed the volunteer call center system for the Israeli government, we would be happy to translate it to other languages, adjust it to other government regulations, and assist in implementing it, if requested. Please contact us if we can be of service in your region.

It is fortunate that the tools we are building can make an impact in these challenging times and we are very proud to help.


This post was written by Yoav Abrahami and originally posted on Corvid's Blog

You can follow him on Twitter



For more engineering updates and insights:


0 views
  • Black Twitter Icon
  • Black YouTube Icon

At Wix Engineering we develop some of the most innovative cloud-based web applications that influence our +180 million users worldwide.

Have any questions?
Email: wixeng@wix.com

Trademarks and logos of other parties appearing in this post are the property of their respective holders.

Get Wix Engineering Straight to Your e-mail