190 | ) : null;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Secrets: A Firebase feature teacher
2 |
3 | We're going to workshop our way through to a functional web application built on the Firebase platform.
4 |
5 | We'll try to abstract away as much of the non-Firebase code as possible so that we can focus on the Firebase.
6 |
7 | ## Dependencies
8 |
9 | You'll need to [install node](https://nodejs.org/en/download/), which comes bundled with the [npm](https://docs.npmjs.com/getting-started/what-is-npm) package manager for Node.js dependencies. Node version 8.12.0 is ideal, but any Node version 8.x should work. Run `node --version` to verify that you're on the right version of Node. You can use [nvm for Unix](https://github.com/creationix/nvm) or [nvm for Windows](https://github.com/coreybutler/nvm-windows) to manage your local Node version.
10 |
11 | We'll use a Node package named [npx](https://github.com/zkat/npx) to run some of our Node.js executables.
12 |
13 | `npx` is Node.js package runner written with--of course!--Node.js. `npx` can find an executable from either the local or the global `node_modules` folder. It will attempt to find the executable dependency locally, then globally. If that fails, npx will install it into a global cache and run it from there.
14 |
15 | ## Create a Firebase project
16 |
17 | - Visit `https://console.firebase.google.com/` and create a project.
18 | - Name the project whatever you'd like. The options here don't matter much.
19 |
20 | ## Enable the Firebase Tools CLI
21 |
22 | - Install `npx` with `npm install --global npx`
23 | - `npx firebase login` to log in with the same Google account that you used for your Firebase project
24 |
25 | The `firebase-tools` CLI can now be accessed by running `npx firebase`.
26 |
27 | ## Installation for completed app
28 |
29 | - `npm install --global npx` to install npx
30 | - `git checkout complete` to check out the completed branch
31 | - `npm install` to install the front-end dependencies
32 | - `cd functions && npm install` to install Cloud Functions dependencies
33 | - `cd functions && npm test` to run Cloud Functions tests
34 | -
35 | - `npm run-script deploy` to deploy the Firebase app
36 |
37 | ## Installation for workshop
38 |
39 | - `npm install --global npx` to install npx
40 | - `git checkout master` to check out the workshop starting point
41 | - `npm install` to install the front-end dependencies
42 | - `npx firebase init`
43 | - Use the arrow keys to install all of the Firebase CLI features
44 | - Select the project that you're using for this workshop
45 | - Accept the defaults
46 | - You'll know you're done when you see `+ Firebase initialization complete!` in your terminal
47 |
48 | ## firebase init
49 |
50 | If you accepted the defaults, then `npx firebase init` will have installed the following files:
51 |
52 | - `/functions`: Your Cloud Functions code
53 | - `/public`: The public files to be deployed on Firebase Hosting
54 | - `.firebaserc`: Firebase project definition
55 | - `database.rules.json`: Realtime Database (aka the RTDB) security rules
56 | - `firebase.json`: Firebase project config
57 | - `firestore.indexes.json`: Firestore indexes
58 | - `firestore.rules`: Firestore security rules
59 | - `storage.rules`: Firebase Storage security rules
60 |
61 | We'll start the workshop with these files in their default states. The only project-specific file in here is `.firebaserc`, which you can edit to point the `firebase-tools` CLI to any Firebase project for which you have access.
62 |
63 | Check out `.firebaserc.dist` for an example of the file.
64 |
65 | ### Check your installation
66 |
67 | Run `npx firebase serve` to run a local Firebase Hosting emulator. Open [http://localhost:5000/](http://localhost:5000/) to see what you have so far.
68 |
69 | The page you see is served from the `/public` folder. We'll be overwriting these files soon, so don't get attached.
70 |
71 | ## Run local dev server
72 |
73 | We're using the [Parcel](https://parceljs.org/getting_started.html) app bundler and dev server.
74 |
75 | Parcel is mostly automated, so there isn't much to manage yourself. The Parcel commands that we'll use are within the `package.json` scripts and can be called with `npm run-script serve` and `npm run-script build`.
76 |
77 | Run `npm run-script serve` to get your local dev server running. The terminal will tell you which port on `localhost` to use to view your page. The default url is [http://localhost:1234/](http://localhost:1234/)
78 |
79 | ## Test your Firebase Hosting deploy
80 |
81 | 1. Run `npm run-script build` top populate your `/public` folder with the build app files.
82 | 2. Run `npx firebase deploy --only hosting` to deploy only Firebase Hosting.
83 | 3. See the "Hosting URL" output by your terminal and follow that URL to test your deploy.
84 | 4. Add `/__/firebase/init.js` to your hosting URL and open that page to see your Firebase Hosting initialization. Example: `https://how-to-firebase-secrets.firebaseapp.com/__/firebase/init.js`.
85 |
86 | The `/__/firebase/init.js` file is available after your first Firebase Hosting deploy. This allows for a very handy pattern where you merely reference the `init.js` file in a script tag on your page and you're automatically initialized wherever you deploy your app on Firebase Hosting.
87 |
88 | This is a bit of an advanced tricky, but let's do it!
89 |
90 | ## Add Firebase to your project
91 |
92 | Open up the [Firebase web setup docs](https://firebase.google.com/docs/web/setup) and scroll down to the CDN script tags. Copy the entire block and paste it into your `src/index.html` file at the bottom of the `` tag.
93 |
94 | We'll use `firebase-app.js`, `firebase-auth.js`, `firebase-firestore.js` and `firebase-functions.js` scripts. So go ahead and comment out or delete the other script tags.
95 |
96 | Finally, notice the script tag that contains `firebase.initializeApp(config)`. Replace the guts of that `
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
131 |
132 | ```
133 |
134 | Check your setup by opening up Chrome DevTools on your dev page and typing `firebase.app().options`. This will output the config for your Firebase app. Just make sure that it looks right, and you're good.
135 |
136 | # Firebase Authentication
137 |
138 | Now that we have the Firebase SDK on our page, we can implement auth in just three easy steps.
139 |
140 | ## Task 1: Implement signInWithPopup
141 |
142 | **File:** `src/components/login.js`
143 |
144 | We're going to wire up the "Log in with Google" button to Firebase Authentication.
145 |
146 | You can find the completed code on the `complete` branch of this repo: [login.js](https://github.com/how-to-firebase/secrets/blob/complete/src/components/login.js)
147 |
148 | ## Task 2: Implement onAuthStateChange listener
149 |
150 | **File:** `src/components/app.js`
151 |
152 | Firebase has a `currentUser` object that represents the logged-in user's [JWT](https://jwt.io/).
153 |
154 | We'll need to sync the `currentUser` JWT to our app's state.
155 |
156 | You can find the completed code on the `complete` branch of this repo: [app.js](https://github.com/how-to-firebase/secrets/blob/complete/src/components/app.js)
157 |
158 | ## Task 3: Implement signOut
159 |
160 | **File:** `src/components/logged-in.js`
161 |
162 | Signing out with Firebase Authentication is EASY!
163 |
164 | You can find the completed code on the `complete` branch of this repo: [logged-in.js](https://github.com/how-to-firebase/secrets/blob/complete/src/components/logged-in.js)
165 |
166 | ## Task 4: Prepare to add a Firestore record
167 |
168 | **File:** `src/database/add-vault.js`
169 |
170 | You won't be able to actually add a record until you've completed Task 5. This step succeeds when you get your first `Missing or insufficient permissions` error.
171 |
172 | You can find the completed code on the `complete` branch of this repo: [add-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/add-vault.js)
173 |
174 | ## Task 5: Add your first security rule
175 |
176 | **File:** `firestore.rules`
177 |
178 | You created `firestore.rules` earlier when calling `npx firebase init`. `firestore.rules` contains the security rules that you'll need to secure your Firestore database.
179 |
180 | Review [Firestore security rules](https://firebase.google.com/docs/firestore/security/get-started) to see how flexible they can be.
181 |
182 | We're not going to go deep into security rules. That would be a different workshop. So just make sure that your `firestore.rules` file looks like this:
183 |
184 | ```
185 | service cloud.firestore {
186 | match /databases/{database}/documents {
187 | match /user-owned/{uid}/vaults/{vaultId} {
188 | allow read, write: if request.auth.uid == uid
189 | }
190 | }
191 | }
192 | ```
193 |
194 | Then call `npx firebase deploy --only firestore` to deploy your Firestore rules.
195 |
196 | Now you can add the vaults that you wanted in Task 4!
197 |
198 | ## Task 6: Query your vaults
199 |
200 | **File:** `firestore.rules`
201 |
202 | You created `firestore.rules` earlier when calling `npx firebase init`. `firestore.rules` contains the security rules that you'll need to secure your Firestore database.
203 |
204 | Review [Firestore security rules](https://firebase.google.com/docs/firestore/security/get-started) to see how flexible they can be.
205 |
206 | ## Task 7: Listen to vault changes
207 |
208 | **File:** `src/database/sync-vault.js`
209 |
210 | You can find the completed code on the `complete` branch of this repo: [sync-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/sync-vault.js)
211 |
212 | ## Task 8: Update the vault
213 |
214 | **File:** `src/database/update-vault.js`
215 |
216 | You can find the completed code on the `complete` branch of this repo: [update-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/update-vault.js)
217 |
218 | ## Task 9: Call the `encrypt` Cloud Function
219 |
220 | **File:** `src/database/encrypt-vault.js`
221 |
222 | You can find the completed code on the `complete` branch of this repo: [encrypt-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/encrypt-vault.js)
223 |
224 | ## Task 10: Configure Jest
225 |
226 | **File:** `functions/package.json`
227 |
228 | All of the Cloud Functions code lives in this folder, which is a separate NPM project with its own `package.json`. We'll need to make sure that Jest is installed, and we'll want to configure a test command.
229 |
230 | 1. Run `cd functions` to begin work on your Cloud Functions.
231 | 2. `npm install --save-dev jest` to get the Jest test runner.
232 | 3. Add a `"scripts"` attribute to `package.json` and add `"test": "jest --watchAll"` to scripts.
233 | 4. Add an `"engines"` attribute with `"node": "8"` to make sure that our functions run in the Node v8 runtime instead of the default v6.
234 |
235 | The result should look like this:
236 |
237 | ```json
238 | {
239 | "name": "functions",
240 | "description": "Cloud Functions for Firebase",
241 | "scripts": {
242 | "test": "jest --watchAll",
243 | "serve": "firebase serve --only functions",
244 | "shell": "firebase functions:shell",
245 | "start": "npm run shell",
246 | "deploy": "firebase deploy --only functions",
247 | "logs": "firebase functions:log"
248 | },
249 | "dependencies": {
250 | "firebase-admin": "~6.0.0",
251 | "firebase-functions": "^2.1.0"
252 | },
253 | "engines": {
254 | "node": "8"
255 | },
256 | "private": true,
257 | "devDependencies": {
258 | "jest": "^23.6.0"
259 | }
260 | }
261 | ```
262 |
263 | The rest of this file is auto-generated by `npx firebase init`, so don't worry about it. The other "scripts" commands are useful but outside the scope of this workshop.
264 |
265 | 4. Run `npm test` to verify that Jest gets called. Type `q` to quit.
266 |
267 | You can find the completed code on the `complete` branch of this repo: [package.json](https://github.com/how-to-firebase/secrets/blob/complete/functions/package.json)
268 |
269 | ## Task 11: Initialize test environment
270 |
271 | **File:** `functions/src/encrypt.spec.js`
272 |
273 | 1. Download a service account for your project: [service account download instructions](https://firebase.google.com/docs/admin/setup#add_firebase_to_your_app)
274 | 2. Move the service account file to `functions/service-account.json`. This file will be ignored by `git`, so it won't ever make it into your source control. Guard this file carefully, because it grants admin rights to your Firebase project.
275 | 3. Copy the `databaseURL` value from the Firebase Admin SDK screen where you just downloaded your service account.
276 | 4. Update the `databaseURL` values in `functions/environments/environment.js` and `functions/environments/environment.test.js`.
277 | 5. Run `npm test` to verify that the `#encrypt -> setup` tests pass.
278 | 6. Open up `functions/utilities/test-context.js` and `functions/jest.config.js` and read the comments at the top of each file.
279 |
280 | The Cloud Functions runtime provides an initialized Firebase `admin` app, but we don't have that in our testing environment. Therefore we need to create our own `admin` app with a service account.
281 |
282 | We're also setting up basic environment files to add to our `context` object. This `context` object is arbitrary. We just made it up. But most use cases of Cloud Functions will need environment variables and an `admin` app, so we're starting with a robust architecture.
283 |
284 | ## Task 12: Encrypt the secret
285 |
286 | **File:** `functions/encrypt.js`
287 |
288 | You can find the completed code on the `complete` branch of this repo: [encrypt.js](https://github.com/how-to-firebase/secrets/blob/complete/functions/src/encrypt.js)
289 |
290 | ## Task 13: Export `encrypt` to the Cloud Functions runtime
291 |
292 | **File:** `functions/index.js`
293 |
294 | 1. Import `../utilities/prod-context` as your production `context`.
295 | 2. Import `Encrypt` from `./src/encrypt`
296 | 3. Instantiate an instance of our `encrypt` function using `Encrypt(context)`.
297 | 4. Export a callable Cloud Function to `exports.encrypt` using [the docs](https://firebase.google.com/docs/functions/callable#write_and_deploy_the_callable_function) as a guide.
298 | 5. Run `npm install` and `npx firebase deploy --only functions` to deploy.
299 | 6. Open up the Functions logs in your Firebase Console to confirm that the deploy succeeded.
300 | 7. Use the running `localhost` version of the app to attempt to encrypt a secret.
301 | 8. Verify that the `encrypted` string was saved to Firestore.
302 | 9. Watch the Functions logs to see each call to `encrypt` succeed.
303 |
304 | You can find the completed code on the `complete` branch of this repo: [index.js](https://github.com/how-to-firebase/secrets/blob/complete/functions/index.js)
305 |
306 | ## Task 14: Call the `decrypt` Cloud Function
307 |
308 | **File:** `src/database/decrypt-vault.js`
309 |
310 | You can find the completed code on the `complete` branch of this repo: [decrypt-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/decrypt-vault.js)
311 |
312 | ## Task 15: Decrypt the vault
313 |
314 | **File:** `functions/src/decrypt.js`
315 |
316 | You can find the completed code on the `complete` branch of this repo: [decrypt.js](https://github.com/how-to-firebase/secrets/blob/complete/functions/src/decrypt.js)
317 |
318 | ## Task 16: Export `decrypt` to the Cloud Functions runtime
319 |
320 | **File:** `functions/index.js`
321 |
322 | 2. Import `Decrypt` from `./src/encrypt`
323 | 3. Instantiate an instance of our `decrypt` function using `Decrypt(context)`.
324 | 4. Export a callable Cloud Function to `exports.decrypt` using [the docs](https://firebase.google.com/docs/functions/callable#write_and_deploy_the_callable_function) as a guide.
325 | 5. Run `npm install` and `npx firebase deploy --only functions` to deploy.
326 | 6. Open up the Functions logs in your Firebase Console to confirm that the deploy succeeded.
327 | 7. Use the running `localhost` version of the app to attempt to encrypt a secret.
328 | 8. That clicking the 'DECRYPT' button in the UI will decrypt a record.
329 | 9. Watch the Functions logs to see each call to `decrypt` succeed.
330 |
331 | You can find the completed code on the `complete` branch of this repo: [index.js](https://github.com/how-to-firebase/secrets/blob/complete/functions/index.js)
332 |
333 | ## Task 17: Delete a Firestore record
334 |
335 | **File:** `src/database/remove-vault.js`
336 |
337 | You can find the completed code on the `complete` branch of this repo: [remove-vault.js](https://github.com/how-to-firebase/secrets/blob/complete/src/database/remove-vault.js)
338 |
--------------------------------------------------------------------------------