Last active
June 22, 2022 21:04
-
-
Save katowulf/7b0ec962063626acbf73512d296c586e to your computer and use it in GitHub Desktop.
Example of validating Firestore writes using Cloud Functions endpoints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import './style.css'; | |
import logger from './logger'; // see https://gist.github.com/katowulf/08cd54013ad75f2c4d6cc9961ec77db1 | |
import {sendRequest} from './request'; | |
const endpoint = 'https://us-central1-YOUR_PROJECT_ID_HERE.cloudfunctions.net/validateRequest'; | |
const data = { | |
string: 'foo', | |
integer: 23, | |
boolean: false, | |
timestamp: Date.now() // gets converted in Functions | |
}; | |
logger.log('Sending HTTP request to Cloud Functions endpoint'); | |
sendRequest('POST', endpoint, data) | |
.then(req => logger.log('server response: ', req.responseText || req.statusText)) | |
.catch(e => logger.error(e)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////////////// | |
// Make an XHR request to a Firebase endpoint. | |
// ♡ Firebase | |
/////////////////////////////////// | |
import logger from './logger'; // see https://gist.github.com/katowulf/08cd54013ad75f2c4d6cc9961ec77db1 | |
export function sendRequest(method, endpoint, data=null, token=null) : Promise<any> { | |
return new Promise((resolve, reject) => { | |
logger.debug('Sending', method, ' request to ', endpoint); | |
var req = new XMLHttpRequest(); | |
req.onload = function() { | |
logger.debug(method, 'response: ', req.responseText || req.statusText); | |
resolve(req); | |
} | |
req.onerror = function() { | |
logger.error(method + ' request failed.', req.statusText); | |
reject(req.statusText); | |
} | |
req.open(method, endpoint, true); | |
req.setRequestHeader("Content-Type", "application/json"); | |
if( token ) { | |
req.setRequestHeader('Authorization', 'Bearer ' + token); | |
} | |
req.send(JSON.stringify(data)); | |
}); | |
} | |
export function sendAuthenticatedRequest(method, endpoint, data=null) : Promise<any> { | |
const user = firebase.auth().currentUser; | |
if( !user ) { | |
return Promise.reject('Authenticate before calling authenticateRequest()'); | |
} | |
return user.getIdToken().then(function(token) { | |
return sendRequest(method, endpoint, data, token); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as functions from 'firebase-functions'; | |
import * as admin from 'firebase-admin'; | |
import {Validator} from './Validator'; | |
const cors = require('cors')({ | |
origin: true, | |
}); | |
admin.initializeApp(); | |
// Example of JS validation using Functions | |
// See https://stackblitz.com/edit/typescript-nwc4cc?file=index.ts | |
export const validateRequest = functions.https.onRequest((req, res) => { | |
return cors(req, res, () => { | |
const path = 'path/to/document'; | |
const validator = new Validator(); | |
validator.addField('string', 'String', 'string', NaN, 10); | |
validator.addField('integer', 'Integer', 'number', 0, 1000); | |
validator.addField('boolean', 'Boolean', 'boolean'); | |
validator.addField('timestamp', 'Timestamp', 'number', 0); | |
console.log('req.body', req.body); | |
console.log('req.params', req.params); | |
const data = Object.assign({}, req.body); | |
const errors = validator.validate(data); | |
if (errors.length) { | |
res.send({status: 'invalid', errors: errors}); | |
} else { | |
// convert timestamps | |
data.timestamp = admin.firestore.Timestamp.fromDate(new Date(data.timestamp)); | |
admin.firestore().doc(path).set(data) | |
.then(() => res.send({status: 'success'})) | |
.catch(e => res.send({status: 'error', error: e})); | |
} | |
res.send({status: errors.length ? 'error' : 'success', errors: errors.length ? errors : null}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class Validator { | |
fields: any[]; | |
constructor() { | |
this.fields = []; | |
} | |
addField(key: string, name: string, type: string, min=NaN, max=NaN) { | |
this.fields.push({key: key, name: name, type: type, min: min, max: max}); | |
} | |
validate(data: any): string[] { | |
const errors: string[] = []; | |
this.fields.forEach(field => Validator.validateField(field, data, errors)); | |
return errors; | |
} | |
private static validateField(field: any, data: any, errors: string[]) { | |
if( !data.hasOwnProperty(field.key) ) { | |
errors.push(`${field.name} is required`); | |
} | |
else if( typeof data[field.key] !== field.type ) { | |
errors.push(`${field.name} must be of type ${field.type}`); | |
} | |
else { Validator.checkSize(field, data[field.key], errors); } | |
} | |
private static checkSize(field: any, value: any, errors: string[]) { | |
switch(field.type) { | |
case 'string': | |
if( !isNaN(field.min) && value.length < field.min ) { | |
errors.push(`${field.name} must be at least ${field.min} characters`); | |
} | |
else if( !isNaN(field.max) && value.length > field.max ) { | |
errors.push(`${field.name} cannot be more than ${field.max} characters`); | |
} | |
break; | |
case 'number': | |
if( !isNaN(field.min) && value < field.min ) { | |
errors.push(`${field.name} cannot be less than ${field.min}`); | |
} | |
else if( !isNaN(field.max) && value > field.max ) { | |
errors.push(`${field.name} cannot be longer than ${field.max}`); | |
} | |
break; | |
default: | |
// nothing to do | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment