I censored some code because I put all my Firebase code in a single file.
You should probably change adminuid to something else should you use this.
Try F Word here.
| { | |
| "rules": { | |
| "leaderboard": { | |
| ".read": "auth !== null && auth.uid === 'adminuid'", | |
| ".write": "auth !== null && auth.uid === 'adminuid'" | |
| }, | |
| "fgr-leaderboard": { | |
| ".read": "auth !== null && auth.uid === 'adminuid'", | |
| ".write": "auth !== null && auth.uid === 'adminuid'" | |
| }, | |
| "f-words": { | |
| ".read": "auth !== null && auth.uid === 'adminuid'", | |
| ".write": "auth !== null && auth.uid === 'adminuid'" | |
| } | |
| } | |
| } |
| { | |
| "database": { | |
| "rules": "database.rules.json" | |
| }, | |
| "hosting": { | |
| "public": "public", | |
| "ignore": [ | |
| "firebase.json", | |
| "**/.*", | |
| "**/node_modules/**" | |
| ], | |
| "rewrites": [ { | |
| "source": "/fword", "function": "fword" | |
| } ] | |
| } | |
| } |
| const functions = require('firebase-functions'); | |
| const admin = require("firebase-admin"); | |
| const cors = require('cors')({origin: true}); | |
| const md5 = require('md5'); | |
| admin.initializeApp({ | |
| databaseURL: "https://test-9d9aa.firebaseio.com", | |
| databaseAuthVariableOverride: { | |
| uid: "adminuid" | |
| } | |
| }); | |
| const db = admin.database(); | |
| const fWordDB = db.ref('/f-words'); | |
| // lowercase letters, spaces ok, but no double spaces, cannot start/end with spaces | |
| const usernameValidator = /^([a-z]([a-z]| (?! ))+[a-z]|[a-z]+)$/; | |
| const pfpValidator = /^\d{1,3}(\.[a-z0-9]{6}){2}$/i; | |
| function getVal(ref) { | |
| return new Promise((res, rej) => { | |
| ref.once("value", res, rej); | |
| }); | |
| } | |
| exports.fword = functions.https.onRequest((req, res) => { | |
| return cors(req, res, () => { | |
| function announceError(err) { | |
| res.status(400).send(err instanceof Error ? err.message : err); | |
| } | |
| const now = Date.now(); | |
| res.set('Access-Control-Allow-Origin', '*'); | |
| if (req.query.session) { | |
| const sessionRef = fWordDB.child('sessions/' + req.body.session); | |
| getVal(sessionRef).then(snap => { | |
| return snap.exists() ? snap : Promise.reject('session doesn\'t exist'); | |
| }).then(async snap => { | |
| const {user, expiration_date} = snap.val(); | |
| if (expiration_date !== -1 && expiration_date < now) { | |
| sessionRef.remove(); | |
| return Promise.reject('expired session'); | |
| } else { | |
| if (req.query.type === 'signout') { | |
| // ?session=true&type=signout | |
| sessionRef.remove(); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'setexpiry') { | |
| // ?session=true&type=setexpiry | |
| // {time} | |
| const time = +req.body.time || -1; | |
| if (time !== -1 && time < 300000) return Promise.reject('too short'); | |
| fWordDB.child('users/' + user + '/expiry_length').set(time); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'setbio') { | |
| // ?session=true&type=setbio | |
| // {content} | |
| if (req.body.content.length > 500) return Promise.reject('too long'); | |
| fWordDB.child('users/' + user + '/bio').set(req.body.content); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'setpfp') { | |
| // ?session=true&type=setpfp | |
| // {newpfp} | |
| if (!pfpValidator.test(req.body.newpfp)) return Promise.reject('not a pfp'); | |
| fWordDB.child('users/' + user + '/pfp').set(req.body.newpfp); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'setpass') { | |
| // ?session=true&type=setpass | |
| // {password} | |
| if (!req.body.password.includes(' ')) return Promise.reject('dumb password'); | |
| fWordDB.child('passwords/' + user).set(md5(req.body.password + user)); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'setsecret') { | |
| // ?session=true&type=setsecret | |
| // {content} | |
| if (req.body.content.length > 500) return Promise.reject('too long'); | |
| fWordDB.child('users/' + user + '/secret').set(req.body.content); | |
| res.status(200).end(); | |
| } else if (req.query.type === 'secret') { | |
| // ?session=true&type=secret | |
| res.status(200).send((await getVal(fWordDB.child('users/' + user + '/secret'))).val()); | |
| } else if (req.query.type === 'post') { | |
| // ?session=true&type=post | |
| // {content, parent?} | |
| // returns post ID | |
| const content = req.body.content.trim(); | |
| if (content.length > 500) return Promise.reject('too long'); | |
| else if (content.length <= 0) return Promise.reject('too short'); | |
| const postRef = fWordDB.child('posts').push(); | |
| postRef.set({ | |
| author: user, | |
| content: content, | |
| date: now, | |
| parent: req.body.parent || null | |
| }); | |
| fWordDB.child('users/' + user + '/posts/' + postRef.key).set({content, author: user}); | |
| res.status(200).send(postRef.key); | |
| if (req.body.parent) { | |
| fWordDB.child('posts/' + req.body.parent + '/children/' + postRef.key).set({content, author: user}); | |
| } | |
| } else if (req.query.type === 'like') { | |
| // ?session=true&type=like&post | |
| // returns like count | |
| const author = (await getVal(fWordDB.child('posts/' + req.query.post + '/author'))).val(); | |
| const content = (await getVal(fWordDB.child('posts/' + req.query.post + '/content'))).val(); | |
| if (author === null) return Promise.reject('post does not exist'); | |
| fWordDB.child('posts/' + req.query.post + '/likes/' + user).set(true); | |
| fWordDB.child('users/' + user + '/likes/' + req.query.post).set({content, author}); | |
| res.status(200).send((await getVal(fWordDB.child('users/' + user + '/likes'))).numChildren().toString()); | |
| } else if (req.query.type === 'unlike') { | |
| // ?session=true&type=unlike&post | |
| // returns like count | |
| fWordDB.child('posts/' + req.query.post + '/likes/' + user).remove(); | |
| fWordDB.child('users/' + user + '/likes/' + req.query.post).remove(); | |
| res.status(200).send((await getVal(fWordDB.child('users/' + user + '/likes'))).numChildren().toString()); | |
| } else if (req.query.type === 'user-likes') { | |
| // ?session=true&type=user-likes&post | |
| // returns boolean | |
| res.status(200).send((await getVal(fWordDB.child('users/' + user + '/likes/' + req.query.post))).exists().toString()); | |
| } else if (req.query.type === 'delete') { | |
| // ?session=true&type=delete&post | |
| const admin = (await getVal(fWordDB.child('users/' + user + '/admin'))).val() === 'yes'; | |
| if (!admin) return Promise.reject('not admin'); | |
| const postRef = fWordDB.child('posts/' + req.query.post); | |
| const postData = (await getVal(postRef)).val(); | |
| if (postData.likes) { | |
| Object.keys(postData.likes).forEach(liker => { | |
| fWordDB.child('users/' + liker + '/likes/' + req.query.post).remove(); | |
| }); | |
| } | |
| fWordDB.child('trash/' + req.query.post).set(postData); | |
| postRef.remove(); | |
| fWordDB.child('users/' + postData.author + '/posts/' + req.query.post).remove(); | |
| res.status(200).json(postData); | |
| } else { | |
| // ?session=true | |
| // returns username | |
| res.status(200).send(user); | |
| } | |
| } | |
| }).catch(announceError); | |
| } else { | |
| if (req.query.type === 'signin') { | |
| // ?type=signin | |
| // {user, password} | |
| // returns session ID | |
| const user = req.body.user; | |
| const password = md5(req.body.password + user); | |
| getVal(fWordDB.child('passwords/' + user)).then(snap => { | |
| const actualPassword = snap.val(); | |
| if (actualPassword === password) { | |
| return getVal(fWordDB.child('users/' + user + '/expiry_length')); | |
| } else { | |
| return Promise.reject('incorrect password'); | |
| } | |
| }).then(snap => { | |
| const expiryLength = snap.val(); | |
| const session = md5(now + user + Math.random()); | |
| fWordDB.child('sessions/' + session).set({ | |
| expiration_date: expiryLength === -1 ? -1 : expiryLength + now, | |
| user: user | |
| }); | |
| res.status(200).send(session); | |
| }).catch(announceError); | |
| } else if (req.query.type === 'createuser') { | |
| // ?type=createuser | |
| // {user, password} | |
| // returns session ID | |
| const user = req.body.user; | |
| if (user.length < 1 || user.length > 26 || !usernameValidator.test(user)) { | |
| res.status(400).send('dumb username'); | |
| return; | |
| } | |
| if (!req.body.password.includes(' ')) { | |
| res.status(400).send('dumb password'); | |
| return; | |
| } | |
| const password = md5(req.body.password + user); | |
| const passwordRef = fWordDB.child('passwords/' + user); | |
| getVal(passwordRef).then(snap => { | |
| if (snap.exists()) return Promise.reject('user exists'); | |
| else { | |
| passwordRef.set(password); | |
| const colour = Math.floor(Math.random() * 0x1000000).toString(16).padStart(6, '0'); | |
| fWordDB.child('users/' + user).set({ | |
| bio: 'Hi! I\'m so new that I haven\'t even set my bio yet!', | |
| expiry_length: -1, | |
| joinDate: now, | |
| pfp: Math.floor(Math.random() * 360) + '.' + colour + '.' + colour, | |
| secret: '' | |
| }); | |
| const session = md5(now + user + Math.random()); | |
| fWordDB.child('sessions/' + session).set({ | |
| expiration_date: -1, | |
| user: user | |
| }); | |
| res.status(200).send(session); | |
| } | |
| }).catch(announceError); | |
| } else if (req.query.type === 'user') { | |
| // ?type=user&user | |
| // returns user bio, profile picture, join date | |
| getVal(fWordDB.child('users/' + req.query.user)).then(snap => { | |
| if (!snap.exists()) return Promise.reject('user doesn\'t exist'); | |
| const {bio, pfp, joinDate} = snap.val(); | |
| res.status(200).json({bio, pfp, joinDate}); | |
| }).catch(announceError); | |
| } else if (req.query.type === 'post') { | |
| // ?type=post&post | |
| // returns post data | |
| getVal(fWordDB.child('posts/' + req.query.post)).then(snap => { | |
| if (!snap.exists()) return Promise.reject('post doesn\'t exist'); | |
| res.status(200).json(snap.val()); | |
| }).catch(announceError); | |
| } else if (req.query.type === 'pfps') { | |
| // ?type=pfps&users | |
| // returns post data | |
| const pfps = {}; | |
| Promise.all(req.query.users.split('.').map(async user => { | |
| const pfp = await getVal(fWordDB.child('users/' + user + '/pfp')); | |
| if (pfp) pfps[user] = pfp; | |
| })).then(() => { | |
| res.status(200).json(pfps); | |
| }); | |
| } else if (req.query.type === 'recent-posts') { | |
| // ?type=recent-posts&limit&from | |
| // returns posts | |
| const limit = Math.round(+req.query.limit); | |
| if (isNaN(limit)) res.status(400).send("limit not a number"); | |
| else if (limit < 1) return res.status(400).send("limit too small"); | |
| else if (limit > 100) return res.status(400).send("limit too big"); | |
| let ref = fWordDB.child('posts').orderByKey(); | |
| if (req.query.from) ref = ref.endAt(req.query.from); | |
| getVal(ref.limitToLast(limit)).then(snap => { | |
| res.status(200).json(snap.val()); | |
| }).catch(announceError); | |
| } else if (req.query.type === 'user-posts') { | |
| // ?type=user-posts&user | |
| // returns post previews | |
| getVal(fWordDB.child('users/' + req.query.user + '/posts')).then(snap => { | |
| if (snap.exists()) res.status(200).json(snap.val()); | |
| else res.status(200).json({}); | |
| }).catch(announceError); | |
| } else if (req.query.type === 'user-likes') { | |
| // ?type=user-likes&user | |
| // returns post previews | |
| getVal(fWordDB.child('users/' + req.query.user + '/likes')).then(snap => { | |
| if (snap.exists()) res.status(200).json(snap.val()); | |
| else res.status(200).json({}); | |
| }).catch(announceError); | |
| } else { | |
| // returns "Hello!" | |
| res.status(200).send("Hello!"); | |
| } | |
| } | |
| }); | |
| }) |