WorryFree Computers   »   [go: up one dir, main page]

Tom Larkworthy
Tom Larkworthy
Engineer

Securing your Firebase Realtime Database just got easier with our newest feature: query-based rules. Query-based rules allow you to limit access to a subset of data. Need to restrict a query to return a maximum of 10 records? Want to ensure users are only retrieving the first 20 records instead of the last 20? Want to let a user query for only their documents? Not a problem. Query-based rules has you covered. Query-based rules can even help you simplify your data structure. Read on to learn how!

The new query variable

Security rules come with a set of variables that help you protect your data. For instance, the auth variable tells you if a user is authenticated and who they are, and the now allows you to check against the current server time.

Now, with the query variable, you can restrict read access based on properties of the query being issued.

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 100"
}

In the example above a client can read the messages location only if they issue an orderByKey() query that limits the dataset to 100 or less. If the client asks for more than 100 messages the read will fail.

The query variable contains additional properties for every type of query combination: orderByKey, orderByChild, orderByValue, orderByPriority, startAt, endAt, equalTo, limitToFirst, and limitToLast. Using a combination of these properties you can restrict your read access to whatever criteria you need. Get the full reference and see more examples in our docs.

Simplifying data structures

Another benefit of query-based rules is that they make it easier to manage a shallow data structure.

In the past you might index your items location by a user's id.

{
   "items": {
       "user_one": { 
          "item_one": {
             "text": "Query-based, rules!"
          }
       }
    }
}

This structure made it easy to query and restrict item reads on a per-user basis.

{
  "rules": {
    "items": {
      "$uid": {
        ".read": "auth.uid == $uid"
      }
    }
  }
}

This is great because your user's items are secured, but it requires you to index off of the user's id, potentially replicating data or complicating your client code.

With query-based rules, you can now get the same security without the nesting!

{
  "rules": {
     "items": {
        ".read": "auth.uid != null &&
                  query.orderByChild == 'uid' &&
                  query.equalTo == auth.uid"
     }
  }
}

The above rule will restrict any read on a per-user basis without indexing by the "uid" key, which means you can write a simple query to retrieve a user's items without sacrificing security.

Now the data structure is reduced by one level:

```

{
   "items": {
       "item_one": {
          "text": "Query-based, rules!",
          "uid": "user_one"
       }
    }
}

``

db.ref("items").orderByChild("uid")
               .equalTo(auth.currentUser.uid)
               .on("value", cb)  

The above query will retrieve all the items belonging to the currently authenticated user. If a user tries to access items that don't belong to them, the read will fail.

When should I upgrade?

Query expressions are new feature that can be used in parallel with your existing rules. If you find that query expressions can improve your security or simplify your structures you can upgrade to them incrementally.

Give it a try!

Query-based rules are available for use today. Go to your Firebase console and use the emulator to give them a whirl. Check out our official documentation for more information. If you run into any problems make sure to check out our support channels or hit up the Firebase Slack community.

Doug Stevenson
Doug Stevenson
Developer Advocate

When I think back 10 years or so when I was writing code for a monolithic codebase written in C++, one of the worst memories I have of the time was waiting for a build to complete just to run a couple lines of new code. The pain was so common among engineers that it bore the creation of one of the all-time most popular XKCD comics:

This is a great idea, if executed safely.

My office at the time didn't have swords to play with, so waiting for a build to complete was really boring.

The pain is similar if I had to deploy my Cloud Functions code every time I want test a function. Fortunately, the Firebase CLI now has a local emulator that works for both HTTPS and other types of functions. This lets me run the functions on my development machine by emulating the events that would trigger it. You can read more about that in the documentation.

One of the emulator's handy features is the fact that it will reload any JavaScript files that change over time. So, if you modify the code of a function, you can test it right away in the emulator without having to restart it:

  1. Switch to code editor, write JavaScript
  2. Switch to emulator, run code
  3. GOTO 1

It's fast and easy, and keeps you "in the zone".

However, things become less easy when you switch from JavaScript to TypeScript. Running TypeScript on Cloud Functions requires a compilation phase to convert the TypeScript into JavaScript so that it can be executed by the emulator. It adds a bit of a hassle:

  1. Switch to code editor, write TypeScript
  2. Switch to command prompt, execute npm run build to generate JavaScript
  3. Switch to emulator, run code
  4. GOTO 1

There's an easier way to do this that doesn't involve running a command every time. The TypeScript compiler (tsc) has a special "watch mode" that lets it pick up changes to your TypeScript and automatically compile them. You can run it from the command line by changing to your functions folder and running this command:

./node_modules/.bin/tsc --watch

With this running alongside the local emulator, which automatically picks up changes to the compiled JavaScript, your development workflow goes back to this:

  1. Switch to code editor, write TypeScript
  2. Switch to emulator, run code
  3. GOTO 1

And, if you're using VSCode, you can even have it directly run tsc in watch mode. You can start it up with Tasks -> Run Build Task..., then choosing "tsc: watch".

It will fire up a command shell right in the IDE, run tsc --watch, and show you the output. You'll see messages like this, along with any compilation errors, any time a TypeScript file changes:

2:38:38 PM - File change detected. Starting incremental compilation...
2:38:38 PM - Compilation complete. Watching for file changes.

Now, bear in mind that tsc in watch mode won't run TSLint against your code, so be sure to set that up in VSCode so you can always see its recommendations, as I showed in a prior blog post. If you're not linting your code, seriously consider setting that up, because you'll catch potential mistakes that could be costly in production.

Doug Stevenson
Doug Stevenson
Developer Advocate

Are you looking for something new to learn this year? Then let me suggest TypeScript for development with Cloud Functions!

Not long ago, the Cloud Functions for Firebase team released an update to the Firebase CLI that makes it easy for you to write your functions in TypeScript, rather than JavaScript. The Firebase team encourages you to consider switching to TypeScript, but I can imagine you might be reluctant to learn a new language, especially if you're already comfortable with JavaScript. The great news is that TypeScript offers you a bunch of benefits that are easy to start using today.

The language itself offers a bunch of features that make your code easier to read and write, and less error-prone:

  • Static type checking (optional)
  • Classes and interfaces
  • Generics
  • Enums

There's also async/await from ECMAScript 2017, which helps you write asynchronous code more easily. The primary challenge with asynchronous code is management of promises, which is crucial to get right when writing Cloud Functions, but difficult to master. TypeScript makes that much easier.

But what I really want to dive into here is an excellent tool called TSLint that can check your TypeScript code for potential problems before you deploy to Cloud Functions. The Firebase CLI will prompt you to configure TSLint when you initialize Functions in a project using TypeScript, and we strongly recommend that you opt into it.

Enabling TypeScript and TSLint

When you opt into TypeScript and TSLint in your Firebase project structure, the Firebase CLI will add and modify some project files when you run firebase init. First let's take a look at functions/package.json. In there, you'll see the following key:

  "devDependencies": {
    "tslint": "^5.8.0",
    "typescript": "^2.6.2"
  },

This is where node pulls in TypeScript and TSLint for development. Notice that there are "devDependencies" that are separate from the normal "dependencies" that you use in your function code. devDependencies are only stored on your machine, and are made available as tools for development. They are not deployed with your code. Also in that file, notice there are two script definitions:

  "scripts": {
    "lint": "./node_modules/.bin/tslint -p tslint.json",
    "build": "./node_modules/.bin/tsc",
    ...
  }

These give you the ability to run npm run lint and npm run build on the command line from your functions directory. The first check your code with TSLint, and the second will build it with the TypeScript compiler.

The next file to look at is firebase.json. This now has a predeploy hook that runs TSLint against your code, so if it has an error, the deploy will fail:

{
  "functions": {
    "predeploy": [
      "npm --prefix $RESOURCE_DIR run lint",
      "npm --prefix $RESOURCE_DIR run build"
    ]
  }
}

The next file is functions/tsconfig.json. This contains the configuration for the TypeScript compiler.

{
  "compilerOptions": {
    "lib": ["es6"],
    "module": "commonjs",
    "noImplicitReturns": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es6"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

I won't cover all the settings here, but it's important to note that the compiler will look for source TypeScript files under functions/src, and compile them to JavaScript into functions/lib, with ECMAScript 6 compatibility, as required by Cloud Functions. Cloud Functions currently runs Node 6, which means it natively understand ES6 code.

Lastly, take a brief look at functions/tslint.json. This lists all the rules that are observed when TSLint checks your code. You can add new rules here, or remove the rules you don't want. I suggest leaving everything there, as the list of rules was curated by the Firebase team to help with writing functions. The "strict errors" whose values are set to true will cause compile time errors if violated. The warnings below that will just complain about possible issues, and it's the team's opinion that you should look into resolving them.

Show me an example!

Alrightey. Take a look at this function that populates a property called createdAt when a user node is created in Realtime Database. Do you see what's wrong here?

export const '/users/{uid}').onCreate(event => {
    event.data.ref.update({ createdAt: Date.now() })
})

TSLint sees the issue, and its one of the most common mistakes made when writing functions. If you run npm run build on this code, you'll see this error in its output:

Promises must be handled appropriately

This error is triggered by the rule no-floating-promises. TSLint sees that event.data.ref.update returns a promise, but nothing is being done with it. The correct way to deal with promises for database triggers is to return it:

export const '/users/{uid}').onCreate(event => {
    return event.data.ref.update({ createdAt: Date.now() })
})

If you're using async/await, you can also declare the function async and use await to return it:

export const '/users/{uid}').onCreate(async event => {
    await event.data.ref.update({ createdAt: Date.now() })
})

Proper handling of promises is an absolute requirement when dealing with Cloud Functions, and TSLint will point out where you're not doing so.

I want faster feedback!

I do a lot of Android development, and I'm accustomed to Android Studio linting my code as I type it. This is valuable because I get instant feedback about where things could go wrong. On the Firebase team, a bunch of us use VSCode for editing TypeScript, and it will use TSLint to give you instant feedback. The TSLint extension is easy to install.

Go to View -> Extensions. Type "TSLint" into the search box on the left. Find the TSLint extension, and click the Install button. After it's installed, click the Reload button, and now your TypeScript will be marked with possible mistakes.

How about another example?

Here's an HTTPS function that fetches a user document from Firestore using the Admin SDK. It looks OK and works OK:

export const getUser = functions.https.onRequest((req, res) => {
    var uid = req.params.uid
    const doc = admin.firestore().doc(`users/${uid}`)
    doc.get().then(doc => {
        res.send(doc.data())
    }).catch(error => {
        res.status(500).send(error)
    })
})

But when viewed in VSCode with TSLint markers, you'll see that it violates some best practices:

Notice the squiggly lines under var, uid, and doc. If you hover the mouse over the marked code, you'll see a message from TSLint:

That's TSLint using the prefer-const rule to tell you that it's better to use const instead of var to declare values that don't change. It's good for the readability of your code, and also prevents someone from making an accidental change to it later on.

The squiggly line under doc is TSLint using the no-shadowed-variable rule to point out that the doc parameter in the function passed to then() (a DeltaDocumentSnapshot object) is masking the doc constant in the outer scope (a DocumentReference object), making the latter completely unavailable for use. While this is not really a bug here, it can lead to confusion about which doc instance is being referred to at any given moment. Renaming either instance resolves the issue.

Here's a lint-free version of the same function:

export const getUser = functions.https.onRequest((req, res) => {
    const uid = req.params.uid
    const doc = admin.firestore().doc(`users/${uid}`)
    doc.get().then(snapshot => {
        res.send(snapshot.data())
    }).catch(error => {
        res.status(500).send(error)
    })
})

This makes TSLint happy, which should make you happy, because you're writing better code!

If you haven't started learning TypeScript yet, 2018 is a fine time to begin. It's easy to get started, because TypeScript is a strict superset of JavaScript, meaning that all your existing JavaScript code is already valid TypeScript. So you can simply rename your .js files to .ts and drop them into functions/src. Then, you can start using TypeScript language features as you wish. Let us know how it goes and shout out to @Firebase on Twitter.

Doug Stevenson
Doug Stevenson
Developer Advocate

As a Developer Advocate with the Firebase team, one of the fun things I get to do is travel around the world to talk about Firebase and run hackathons for developers. As I reflect on these experiences, it seems to me that the participants at these events have different reasons for being there, and different categories of project ideas.

If you read along here, I’ll encourage you to think about which categories you belong to, or maybe you’re in a category of your own! Then, after you’re done, why don’t you follow this link to Twitter polls where you can tell us how you fit in. I’m curious to see the results!

The first question I’d like to ask is this:

Why do you show up at hackathons? (take the poll)

I’m here for the prizes!

Of course, prizes are a pretty obvious motivator to participate in an event like a hackathon that can take all day and require some fairly difficult work. At DevFest Hamburg 2017 we ran a Firebase AppFest (that’s a lot of fest-ing, yeah?) and I was surprised to see some fantastic prizes:

I’m looking at you, Pixel 2.

The winning team at that event was ecstatic to win, I was told this was an especially big deal because the Pixel 2 was not available in their home country. So, kudos to that team, and enjoy your new phones!

I’m here to learn!

Another motivator for participation in a hackathon is the opportunity to learn new technologies. These folks are not necessarily in it for the prizes - the reward is the knowledge and experience gained from working on a project idea with others. Firebase hackathons are indeed a great place to learn, because the Firebasers present at the event are effectively on-call to answer questions, and get folks unstuck with whatever problems might come up. At AnDevCon DC this year, we held a Firebase + Android Things hackathon, which was a great opportunity for participants to learn about two Google developer technologies at the same time. I was inspired by everyone’s work on this, so I chose to work with Firebase on Android Things during our internal “Firebase Hackweek”. This “doorbell” is what we made.

It turns out that we Firebasers also learn a lot from these events. If there’s something unclear in the documentation, or some API doesn’t work the way you’d initially expect, that becomes real and actionable feedback that we can take to the product teams to further improve the developer experience. And that’s something we take seriously.

I’m here to build with friends!

It comes as no surprise to me that spending time with friends on a shared experience is the only reason you might need to show up at a hackathon. I saw a lot of this at SM Hacks, an event for high school students. I saw many teams there simply enjoying each other’s company while figuring out what to build and how to build it. So many fun and goofy hacks came out of that!

Now here’s the second question I’d like to ask. It’s about how you choose what you want to work on.

Which type of hacker are you? (take the poll)

I’m a “personal hacker”!

This might be the most common type of hacker I’ve seen. Personal hackers build things that they would like to use themselves. I fall squarely into this category most of the time. The main reason I got into mobile development was the idea of programming the little computer in my pocket that I carry around with me everywhere, making it do things that are useful to me.

Some of the useful hacks I’ve seen powered by Firebase are a chord transposer, a to-do manager, and a medication reminder app.

I’m an “opportunity hacker”!

If you see a need in the world for a specific kind of app, then I’ll call you an “opportunity hacker”. I saw this a lot in Manila where we conducted a hackathon for the Firebase support staff. Many of the teams focused on very practical, real-world needs, and built an order-ahead food app, reward points trackers (two teams did this!), and a hardware inventory tracker. These are ideas that could live beyond the end of a hackathon, and become actual services that earn money.

I’m a “technical hacker”!

Technical hackers like projects that explore connections between technologies and solve known technical problems in new ways. Probably the best example I’ve seen of this was the winning project at the Bangkok Firebase AppFest - a Kotlin chatbot that lets you type Kotlin code into it, it evaluates the code using a Google Cloud backend, and sends back the response. I’m not sure I would have ever been able to come up with that idea!

I’m a “fun hacker”!

If you like creating games or apps for entertainment purposes, you’re probably a “fun hacker” by my reckoning. I used to work at a game company, and their hackathons were (of course) totally dominated by games of all varieties. There was one particularly memorable (for me) project in Bangkok where someone used Cloud Functions to progressively un-blur an image of either myself or Sheldon Cooper, and you had to guess which of us was in the picture. That day, I learned that I kinda-sorta look like Sheldon Cooper.

Get out there and hack!

To be honest, I used to dislike the idea of a hackathon because I always felt “dirty” about writing what feels like mostly terrible code to get something done quickly. I’ve always been a big fan of processes like Test Driven Development that yield high code quality, at the expense of some extra time up front. But my experiences with Firebase suggest there is a place for quick hacks alongside disciplined software engineering. And it can be fun and rewarding!

So, what motivates you to go to a hackathon? And what type of hacker are you? Click those links to take a poll on Twitter and let me know!