diff --git a/big-dipper/.meteor/packages b/big-dipper/.meteor/packages index 3e7c0c5fa5..4676390b27 100644 --- a/big-dipper/.meteor/packages +++ b/big-dipper/.meteor/packages @@ -35,3 +35,5 @@ ostrio:flow-router-meta ostrio:flow-router-extra server-render@0.4.0 nimble:restivus +meteorhacks:async +webapp diff --git a/big-dipper/.meteor/versions b/big-dipper/.meteor/versions index 15c3a162ea..7f0b3c408e 100644 --- a/big-dipper/.meteor/versions +++ b/big-dipper/.meteor/versions @@ -48,6 +48,7 @@ localstorage@1.2.0 logging@1.3.1 meteor@1.10.0 meteor-base@1.5.1 +meteorhacks:async@1.0.0 meteortesting:browser-tests@1.3.5 meteortesting:mocha@1.1.5 meteortesting:mocha-core@7.0.1 diff --git a/big-dipper/imports/api/fcmtoken/server/methods.js b/big-dipper/imports/api/fcmtoken/server/methods.js index 8e33ffceee..d187f2307c 100644 --- a/big-dipper/imports/api/fcmtoken/server/methods.js +++ b/big-dipper/imports/api/fcmtoken/server/methods.js @@ -1,104 +1,138 @@ -import { Meteor } from "meteor/meteor"; -import { FCMToken } from "../fcmtoken.js"; -import { admin } from "../../admin.js"; +import { WebApp } from 'meteor/webapp' +import { FCMToken } from '../fcmtoken.js' +import { admin } from '../../admin.js' +import connectRoute from 'connect-route' +import { isString } from 'lodash' // Global API configuration -var Api = new Restivus({ - useDefaultAuth: true, - prettyJson: true, -}); -const StatusOk = 200; -const StatusInvalidInput = 400; -const InternalServerError = 500; -const Success = "Success"; -const Failed = "Failed"; -const BadRequest = "Bad Request"; -const AppCheckFailed = "App Check Failed" +const StatusOk = 200 +const StatusInvalidInput = 400 +const Success = 'Success' +const Failed = 'Failed' +const BadRequest = 'Bad Request' +const AppCheckFailed = 'App Check Failed' -Api.addRoute( - "fcmtoken/update/:address/:token", - { authRequired: false }, - { - //update fcm token against address - post: function () { - if (!Valid(this.urlParams.address) || !Valid(this.urlParams.token)) { - return { - Code: StatusInvalidInput, - Message: BadRequest, - Data: null, - }; +WebApp.connectHandlers.use( + connectRoute(function (router) { + router.post('/fcmtoken/update/:address/:token', async function (req, res) { + // validate that params exist + if (!Valid(req.params.address) || !Valid(req.params.token)) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: BadRequest, + Data: null + }) + ) } + const h = req.headers + // app check header check + if (!h['x-firebase-appcheck']) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) - let h = this.request.headers; - if(!h['x-firebase-appcheck']){ - return { - Code: StatusInvalidInput, - Message: AppCheckFailed, - Data: "x-firebase-appcheck header not found", - }; - } - - // admin.appCheck().verifyToken(h['x-firebase-appcheck']).then((res) => { - var result = updateFCMToken(this.urlParams.address, this.urlParams.token); - - if (result === false) { - return { - Code: InternalServerError, + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: AppCheckFailed, + Data: 'x-firebase-appcheck header missing' + }) + ) + } else { + // performing app check + const appCheckClaims = await verifyAppCheckToken( + h['x-firebase-appcheck'] + ) + + // app check failed + if (!appCheckClaims) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: AppCheckFailed, + Data: 'invalid x-firebase-appcheck header' + }) + ) + } + + const result = updateFCMToken(req.params.address, req.params.token) + + if (result === false) { + res.writeHead(400, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, Message: Failed, - Data: "", - }; - } - - return { + Data: null + }) + ) + } + + res.writeHead(200, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ Code: StatusOk, Message: Success, - Data: result, - }; - - // Useful for future debug - // }).catch((e)=>{ - // return { - // Code: StatusInvalidInput, - // Message: AppCheckFailed, - // Data: "x-firebase-appcheck Failed", - // }; - // }) - - return { - Code: StatusInvalidInput, - Message: BadRequest, - Data: null, - }; - - }, - } -); + Data: null + }) + ) + } + }) + }) +) -function updateFCMToken(userAddress, fcmToken) { +function updateFCMToken (userAddress, fcmToken) { try { FCMToken.upsert( { address: userAddress }, { $set: { address: userAddress, - token: fcmToken, - }, + token: fcmToken + } } - ); + ) } catch (error) { console.log(error) - return false; + return false } - return true; + return true } -function Valid(parameter) { - if (typeof(parameter) != "string"){ +function Valid (parameter) { + if (isString(parameter)) { return false } - if (parameter.length == 0){ + if (parameter.length === 0) { return false } return true } + +async function verifyAppCheckToken (appCheckToken) { + if (!appCheckToken) { + return null + } + try { + const res = await admin.appCheck().verifyToken(appCheckToken) + return res + } catch (err) { + return null + } +} diff --git a/big-dipper/imports/api/notifications/server/methods.js b/big-dipper/imports/api/notifications/server/methods.js index 34c9d02b41..ea58a243f8 100644 --- a/big-dipper/imports/api/notifications/server/methods.js +++ b/big-dipper/imports/api/notifications/server/methods.js @@ -1,86 +1,27 @@ -import { Meteor } from "meteor/meteor"; -import { Notifications } from "../notifications.js"; -import { FCMToken } from "../../fcmtoken/fcmtoken.js"; -import { isNumber } from "lodash"; -import { sanitizeUrl } from "@braintree/sanitize-url"; -import { HTTP } from "meteor/http"; -import {admin} from "../../admin.js" +import { Meteor } from 'meteor/meteor' +import { WebApp } from 'meteor/webapp' +import { Notifications } from '../notifications.js' +import { FCMToken } from '../../fcmtoken/fcmtoken.js' +import { isNumber, isString } from 'lodash' +import { sanitizeUrl } from '@braintree/sanitize-url' +import { HTTP } from 'meteor/http' +import { admin } from '../../admin.js' +import connectRoute from 'connect-route' +const StatusOk = 200 +const StatusInvalidInput = 400 +const Success = 'Success' +const BadRequest = 'Bad Request' +const InvalidID = 'Invalid Notification ID' +const AppCheckFailed = 'App Check Failed' - -const StatusOk = 200; -const StatusInvalidInput = 400; -const InternalServerError = 500; -const Success = "Success"; -const BadRequest = "Bad Request"; -const InvalidID = "Invalid Notification ID"; -const AppCheckFailed = "App Check Failed" - -var Api = new Restivus({ +const Api = new Restivus({ useDefaultAuth: true, - prettyJson: true, -}); + prettyJson: true +}) Api.addRoute( - "notifications/markread", - { authRequired: false }, - { - post: function () { - - let h = this.request.headers; - if(!h['x-firebase-appcheck']){ - return { - Code: StatusInvalidInput, - Message: AppCheckFailed, - Data: "x-firebase-appcheck header missing", - }; - } - - admin.appCheck().verifyToken(h['x-firebase-appcheck']).then((res)=>{ - const notificationIDs = this.bodyParams.notifcationIDs; - - if (notificationIDs && notificationIDs.length > 0) { - for (let index = 0; index < notificationIDs.length; index++) { - const id = notificationIDs[index]; - - //mark as Read - var result = markRead(id); - if (result != 1) { - return { - Code: StatusInvalidInput, - Message: InvalidID, - Data: id, - }; - } - } - - return { - Code: StatusOk, - Message: Success, - Data: "Notifications Marked as Read", - }; - } - }).catch((e)=>{ - return { - Code: StatusInvalidInput, - Message: AppCheckFailed, - Data: "x-firebase-appcheck Failed", - }; - }) - - - - return { - Code: StatusInvalidInput, - Message: BadRequest, - Data: null, - }; - }, - } -); - -Api.addRoute( - "notifications/getAllNotifications/:address/:limit/:offset", + 'notifications/getAllNotifications/:address/:limit/:offset', { authRequired: false }, { get: function () { @@ -90,42 +31,134 @@ Api.addRoute( isNumber(this.urlParams.offset) ) { try { - var res = getNotifications( + const res = getNotifications( this.urlParams.address, this.urlParams.limit, this.urlParams.offset - ); + ) return { Code: StatusOk, Message: Success, - Data: { results: res }, - }; + Data: { results: res } + } } catch (e) { return { Code: StatusInvalidInput, Message: BadRequest, - Data: e, //"Error Fetching Notifcations", - }; + Data: null + } } } return { Code: StatusInvalidInput, Message: BadRequest, - Data: "Invalid Params", - }; - }, + Data: 'Invalid Params' + } + } } -); +) + +WebApp.connectHandlers.use( + connectRoute(function (router) { + router.post('notifications/markread', async function (req, res) { + const h = req.headers + const notificationIDs = req.body.notificationIDs + + if (!h['x-firebase-appcheck']) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: AppCheckFailed, + Data: 'x-firebase-appcheck header missing' + }) + ) + } else { + if (notificationIDs && notificationIDs.length > 0) { + // performing app check + const appCheckClaims = await verifyAppCheckToken( + h['x-firebase-appcheck'] + ) + + // app check failed + if (!appCheckClaims) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: AppCheckFailed, + Data: 'invalid x-firebase-appcheck header' + }) + ) + } + + // app check passed + if (notificationIDs && notificationIDs.length > 0) { + for (let index = 0; index < notificationIDs.length; index++) { + const id = notificationIDs[index] + + // mark as Read + const result = markRead(id) + if (result !== 1) { + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: InvalidID, + Data: `notificationID ${id} is invalid` + }) + ) + } + } + + // Success + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusOk, + Message: Success, + Data: 'notifications marked as Read' + }) + ) + } + } + + // invalid request + res.writeHead(StatusOk, { + 'Content-Type': 'text/html' + }) + + res.end( + JSON.stringify({ + Code: StatusInvalidInput, + Message: BadRequest, + Data: 'notifcationIDs list is missing or corrupt' + }) + ) + } + }) + }) +) Meteor.methods({ //send un settled notifications "Notifications.sendPushNotifications": function () { this.unblock(); - const unSettled = Notifications.find({ settled: false }); - - - + const unSettled = Notifications.find({ settled: false }) + unSettled .forEach((sale) => { var sellerAddress = sale.from; @@ -155,65 +188,75 @@ Meteor.methods({ timeToLive: 86400, }; - if(Meteor.settings.params.sendNotifications === 1){ - + + if (Meteor.settings.params.sendNotifications === 1) { admin - .messaging() - .sendToDevice(token, message, options) - .then((n) => { - markSent(saleID); - console.log(n) - }) - .catch((e) => { - console.log("Notification not sent to ", token); - console.log(e) - }); - + .messaging() + .sendToDevice(token, message, options) + .then((n) => { + markSent(saleID) + console.log(n) + }) + .catch((e) => { + console.log('Notification not sent to ', token) + console.log(e) + }) } - }) - - }, -}); + }) + } +}) -function Valid(parameter) { - if (typeof parameter != "string") { - return false; +function Valid (parameter) { + if (isString(parameter)) { + return false } - if (parameter.length == 0) { - return false; + if (parameter.length === 0) { + return false } - return true; + return true } -function markRead(id) { - return Notifications.update({ _id: id }, { $set: { read: true } }); +function markRead (id) { + return Notifications.update({ _id: id }, { $set: { read: true } }) } -function markSent(id) { - Notifications.update({ _id: id }, { $set: { settled: true } }); - return Notifications.update({ _id: id }, { $set: { settled: true } }); +function markSent (id) { + Notifications.update({ _id: id }, { $set: { settled: true } }) + return Notifications.update({ _id: id }, { $set: { settled: true } }) } -function getNotifications(address, limit, offset) { +function getNotifications (address, limit, offset) { return Notifications.find( { from: address }, { sort:{created_at:-1}, limit: parseInt(limit), - skip: parseInt(offset), + skip: parseInt(offset) } - ).fetch(); + ).fetch() } -function getUserNameInfo(address) { - var result; - var url = sanitizeUrl( +function getUserNameInfo (address) { + let result + const url = sanitizeUrl( `${Meteor.settings.remote.api}/pylons/account/address/${address}` - ); + ) try { - let response = HTTP.get(url); - result = JSON.parse(response.content); + const response = HTTP.get(url) + result = JSON.parse(response.content) } catch (e) { - console.log("error getting userNameInfo: ", e); + console.log('error getting userNameInfo: ', e) + } + return result +} + +async function verifyAppCheckToken (appCheckToken) { + if (!appCheckToken) { + return null + } + try { + const res = await admin.appCheck().verifyToken(appCheckToken) + return res + } catch (err) { + return null } - return result; } diff --git a/big-dipper/package-lock.json b/big-dipper/package-lock.json index 3ba87e4b84..89a88a0bfb 100644 --- a/big-dipper/package-lock.json +++ b/big-dipper/package-lock.json @@ -1536,6 +1536,11 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "connect-route": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/connect-route/-/connect-route-0.1.5.tgz", + "integrity": "sha512-6OZGYeoKS7zzhCAGP+Du7WYjP4geK3ii0sZlS+Oqx+ANFPcS5t/jDJr9BuhgN9qPz1ld8iahiPxNrgJyHeZ3cw==" + }, "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", diff --git a/big-dipper/package.json b/big-dipper/package.json index be3e9fc7c7..177e5904be 100644 --- a/big-dipper/package.json +++ b/big-dipper/package.json @@ -28,6 +28,7 @@ "bootswatch": "^4.6.0", "chart.js": "^3.7.1", "cheerio": "^1.0.0-rc.6", + "connect-route": "^0.1.5", "crypto-js": "^3.3.0", "d3": "^5.16.0", "doc-head-extract": "^1.0.4",