Skip to content

Commit

Permalink
Add a secret check before proceeding
Browse files Browse the repository at this point in the history
This is a hack to support the specific requirements of the NREL publication
process, which is currently intended to be a closed system.
- Users need to scan a specified QR code that represents a secret
- They are allowed to proceed only if the secret matches the app secret

In the long term, we want to use channels directly instead of this "secret"
code. However, we didn't want to deal with externally hosted software for
approval at this time.

This is the commit fixes the first three tasks in
e-mission/e-mission-docs#628

in particular,
e-mission/e-mission-docs#628 (comment)
and
e-mission/e-mission-docs#628 (comment)

Changes:
- new "secretcheck" service
- on receiving a custom URL, pass the parameter to secretcheck
- secretcheck validates the secret and stores it
- change startprefs so that if we have a valid stored secret, we can move on to
  checking the other onboarding states
- copy over the templates and the javascript code for the first screen from the
  em-base repository
  • Loading branch information
shankari committed Mar 16, 2021
1 parent 8aa5997 commit c00f63b
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 9 deletions.
1 change: 1 addition & 0 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<script src="js/splash/referral.js"></script>
<script src="js/splash/customURL.js"></script>
<script src="js/splash/updatecheck.js"></script>
<script src="js/splash/secretcheck.js"></script>
<script src="js/splash/startprefs.js"></script>
<script src="js/splash/pushnotify.js"></script>
<script src="js/splash/storedevicesettings.js"></script>
Expand Down
10 changes: 5 additions & 5 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
angular.module('emission', ['ionic',
'emission.controllers','emission.services', 'emission.plugin.logger',
'emission.splash.customURLScheme', 'emission.splash.referral',
'emission.splash.updatecheck', 'emission.services.email',
'emission.splash.secretcheck', 'emission.services.email',
'emission.intro', 'emission.main',
'pascalprecht.translate'])

.run(function($ionicPlatform, $rootScope, $http, Logger,
CustomURLScheme, ReferralHandler, UpdateCheck) {
CustomURLScheme, ReferralHandler, SecretCheck) {
console.log("Starting run");
// alert("Starting run");
// BEGIN: Global listeners, no need to wait for the platform
Expand All @@ -29,8 +29,8 @@ angular.module('emission', ['ionic',
if (urlComponents.route == 'join') {
ReferralHandler.setupGroupReferral(urlComponents);
StartPrefs.loadWithPrefs();
} else if (urlComponents.route == 'change_client') {
UpdateCheck.handleClientChangeURL(urlComponents);
} else if (urlComponents.route == 'validate_secret') {
SecretCheck.handleValidateSecretURL(urlComponents);
}
});
// END: Global listeners
Expand Down Expand Up @@ -86,7 +86,7 @@ angular.module('emission', ['ionic',
// This cannot directly use plugins - has to check for them first.
.state('splash', {
url: '/splash',
templateUrl: 'templates/splash/splash.html',
templateUrl: 'templates/splash/study-text.html',
controller: 'SplashCtrl'
})

Expand Down
49 changes: 49 additions & 0 deletions www/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,61 @@ angular.module('emission.controllers', ['emission.splash.updatecheck',
.controller('DashCtrl', function($scope) {})

.controller('SplashCtrl', function($scope, $state, $interval, $rootScope,
$ionicPlatform, $ionicPopup, $ionicPopover,
UpdateCheck, StartPrefs, PushNotify, StoreDeviceSettings,
LocalNotify, ClientStats, PostTripAutoPrompt, SurveyLaunch) {
console.log('SplashCtrl invoked');
// alert("attach debugger!");
// PushNotify.startupInit();

/*
* BEGIN: Copy from embase to handle the initial screen
*/
$ionicPlatform.ready(function() {
$scope.scanEnabled = true;
});

$ionicPopover.fromTemplateUrl('templates/splash/about-app.html', {
backdropClickToClose: true,
hardwareBackButtonClose: true,
scope: $scope
}).then(function(popover) {
$scope.popover = popover;
$scope.isIOS = $ionicPlatform.is('ios');
$scope.isAndroid = $ionicPlatform.is('android');
});

$scope.showDetails = function($event) {
$scope.popover.show($event)
}

$scope.hideDetails = function($event) {
$scope.popover.hide($event)
}

$scope.scanCode = function() {
if (!$scope.scanEnabled) {
$ionicPopup.alert({template: "plugins not yet initialized, please retry later"});
} else {
cordova.plugins.barcodeScanner.scan(
function (result) {
if (result.format == "QR_CODE" &&
result.cancelled == false &&
result.text.substring(0,11) == "emission://") {
handleOpenURL(result.text);
} else {
$ionicPopup.alert({template: "invalid study reference "+result.text});
}
},
function (error) {
$ionicPopup.alert({template: "Scanning failed: " + error});
});
}
}; // scanCode
/*
* END: Copy from embase to handle the initial screen
*/

$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
console.log("Finished changing state from "+JSON.stringify(fromState)
+ " to "+JSON.stringify(toState));
Expand Down
54 changes: 54 additions & 0 deletions www/js/splash/secretcheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
angular.module('emission.splash.secretcheck', ['emission.plugin.logger',
'emission.plugin.kvstore'])
.factory('SecretCheck', function($window, $state, $ionicPlatform, $ionicPopup, KVStore, Logger) {

var sc = {};
sc.SECRET_STORE_KEY = "secret_key";
// hardcoded to highlight that this is a hack
// eventually, we want to remove this and use channels like we do for emTripLog
sc.SECRET = "REPLACEME"

sc.handleValidateSecretURL = function(urlComponents) {
Logger.log("handleValidateSecretURL = "+JSON.stringify(urlComponents));
const inputSecret = urlComponents['secret'];
Logger.log("input secret is = "+inputSecret);

const next_state = urlComponents['next_state'] || "root.intro";

if (sc.checkSecret(inputSecret)) {
sc.storeInputSecret(inputSecret).then(() => {
$state.go(next_state);
}).catch((e) =>
Logger.displayError("Could not store valid secret "+inputSecret, e)
);
} else {
Logger.displayError("Invalid input secret",
{message: "Input secret "+inputSecret+" does not match expected "+sc.SECRET,
stack: "In splash/secret.js"});
}
}

sc.checkSecret = function(secret) {
const retVal = secret != null && secret.length > 0 && secret === sc.SECRET;
if (!retVal) {
Logger.log("Secret "+secret+" does not match expected value");
};
return retVal;
}

sc.storeInputSecret = function(secret) {
return KVStore.set(sc.SECRET_STORE_KEY, secret);
}

sc.getInputSecret = function() {
return KVStore.get(sc.SECRET_STORE_KEY).then(function(read_secret) {
return read_secret;
});
}

sc.hasValidSecret = function() {
return sc.getInputSecret().then(sc.checkSecret);
}

return sc;
});
20 changes: 16 additions & 4 deletions www/js/splash/startprefs.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
angular.module('emission.splash.startprefs', ['emission.plugin.logger',
'emission.splash.referral',
'emission.splash.secretcheck',
'emission.plugin.kvstore'])

.factory('StartPrefs', function($window, $state, $interval, $rootScope, $ionicPlatform,
$ionicPopup, KVStore, storage, $http, Logger, ReferralHandler) {
$ionicPopup, KVStore, SecretCheck, storage, $http, Logger, ReferralHandler) {
var logger = Logger;
var nTimesCalled = 0;
var startprefs = {};
Expand Down Expand Up @@ -52,6 +53,12 @@ angular.module('emission.splash.startprefs', ['emission.plugin.logger',
$rootScope.$emit(startprefs.INTRO_DONE_EVENT, currTime);
}

startprefs.readSecretState = function() {
// read secret state from the local KV store
return SecretCheck.hasValidSecret();
}


// returns boolean
startprefs.readIntroDone = function() {
return KVStore.get(INTRO_DONE_KEY).then(function(read_val) {
Expand Down Expand Up @@ -107,8 +114,11 @@ angular.module('emission.splash.startprefs', ['emission.plugin.logger',
*/

startprefs.getPendingOnboardingState = function() {
return startprefs.readStartupState().then(function([is_intro_done, is_consented]) {
if (!is_intro_done) {
return startprefs.readStartupState().then(function([is_intro_done, is_consented, hasReadSecret]) {
if (!hasReadSecret) {
console.assert(!$rootScope.intro_done, "in getPendingOnboardingState first check, hasReadSecret", JSON.stringify(hasReadSecret));
return 'splash';
} else if (!is_intro_done) {
console.assert(!$rootScope.intro_done, "in getPendingOnboardingState first check, $rootScope.intro_done", JSON.stringify($rootScope.intro_done));
return 'root.intro';
} else {
Expand All @@ -133,7 +143,9 @@ angular.module('emission.splash.startprefs', ['emission.plugin.logger',
.then(startprefs.isIntroDone);
var readConsentPromise = startprefs.readConsentState()
.then(startprefs.isConsented);
return Promise.all([readIntroPromise, readConsentPromise]);
var readSecretPromise = startprefs.readSecretState()
.then(startprefs.hasReadSecret);
return Promise.all([readIntroPromise, readConsentPromise, readSecretPromise]);
};

startprefs.getConsentDocument = function() {
Expand Down
41 changes: 41 additions & 0 deletions www/templates/splash/about-app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<ion-popover-view>
<ion-content class="has-footer">
<div class="intro-text">
This app builds an automatic diary of all your trips, across all
transportation modes. It reads multiple sensors, including
location, in the background, and turns GPS tracking on and off
automatically for minimal power consumption. Information about your travel
patterns will be displayed to you according to the design of the study you
join.
</div>

<div class="intro-text">
<b>Tip(s) for correct operation:</b>
<div ng-show="isIOS">
<ion-list>
<ion-item class="item-text-wrap">
<i class="icon ion-checkmark-circled"></i>
"Always allow" access to location
</ion-item>
<ion-item class="item-text-wrap">
<i class="icon ion-checkmark-circled"></i>
Do not force kill the app
</ion-item>
</ion-list>
</div>
<div ng-show="isAndroid">
<ion-list>
<ion-item class="item-text-wrap"> <div>
<i class="icon ion-checkmark-circled"></i>
Keep location services turned on</div>
</ion-item>
</ion-list>
</div>
</div>
</ion-content>
<ion-footer-bar class="bar-positive">
<button class="button button-block button-clear" ng-click="hideDetails($event)">
Close
</button>
</ion-footer-bar>
</ion-popover-view>
45 changes: 45 additions & 0 deletions www/templates/splash/study-text.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<ion-view>
<ion-content class="has-footer">
<!-- <div class="intro-space"></div> -->
<div class="intro-title">
<center><img alt="e-mission icon" src="img/icon.png"/ style="width: 25%; height: auto;"></center>
</div>
<div class="intro-text">
<div>
<center><button class="button button-clear button-dark" ng-click="showDetails($event)"><b>Welcome to emTripLog ℹ️ </b></button></center>
</div>
<!-- -->

To proceed further, you need a link 🔗 or QR code to join a study.
This should have been provided by the researcher who asked you to install this
app.

<div class="intro-space"></div>

<ion-list>
<ion-item class="item-text-wrap">
<div>
<i class="icon ion-iphone"></i>
On your phone 📱, switch back to the browser 🌐, scroll down and
click the link in step 2.
</div>
</ion-item>
<ion-item class="item-text-wrap">
<center> <b> - OR - </b> </center>
</ion-item>
<ion-item class="item-text-wrap">
<div>
<i class="icon ion-laptop"></i>
On your computer 💻, scroll down to step 2 and scan ⬇️ the QR code.
</div>
</ion-item>
</ion-list>
<div class="intro-space"></div>
</div>
</ion-content>
<ion-footer-bar class="bar-positive">
<button ng-enabled="scanEnabled" class="button button-block button-clear" ng-click="scanCode()">
Scan now
</button>
</ion-footer-bar>
</ion-view>

0 comments on commit c00f63b

Please sign in to comment.