Skip to content

Instantly share code, notes, and snippets.

@katowulf
Last active June 22, 2022 21:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save katowulf/7b0ec962063626acbf73512d296c586e to your computer and use it in GitHub Desktop.
Save katowulf/7b0ec962063626acbf73512d296c586e to your computer and use it in GitHub Desktop.
Example of validating Firestore writes using Cloud Functions endpoints
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));
///////////////////////////////////
// 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);
});
}
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});
});
});
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