From 17ac2b23132ee0463ab5eb8b17116c76b32e893e Mon Sep 17 00:00:00 2001 From: james Date: Fri, 18 Jun 2021 01:18:29 +1000 Subject: [PATCH] Firebase protection (finally!) and update README on how to set it all up --- .env | 6 ++-- README.md | 48 ++++++++++++++++++++++++++++ db-example.json | 71 +++++++++++------------------------------- src/api/QuickHitAPI.ts | 26 +++++++++++++--- 4 files changed, 92 insertions(+), 59 deletions(-) diff --git a/.env b/.env index 0e2f92a..4799bb8 100644 --- a/.env +++ b/.env @@ -1,2 +1,4 @@ -REACT_APP_FB_URL=https://table-tennis-elo-default-rtdb.asia-southeast1.firebasedatabase.app/ -REACT_APP_FB_KEY_PATH="fb-key.json" +REACT_APP_FB_URL="" +REACT_APP_FB_API_KEY="" +REACT_APP_FB_SRV_ACC_NAME="" +REACT_APP_FB_SRV_ACC_PW="" \ No newline at end of file diff --git a/README.md b/README.md index e4b99db..9d8220c 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,51 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. + +### Creating a Firebase Realtime Database + +The .env file should contain details for the application to reach your Firebase DB: + +The following is an example file: +```aidl +REACT_APP_FB_URL=https://.asia-southeast1.firebasedatabase.app/ +REACT_APP_FB_API_KEY= +REACT_APP_FB_SRV_ACC_NAME="firebase-admin@.iam.gserviceaccount.com" +REACT_APP_FB_SRV_ACC_PW="" +``` + +### Creating the database +1. Visit https://console.firebase.google.com/u/1/ and select "Add Project" +2. Then select 'Realtime Database' +3. Create your new database, keep all settings default +4. Once complete, the URL provided here will be the value to use as the `REACT_APP_FB_URL` in `.env` +5. Import some dummy data to get the schema going, use the menu and select "Import JSON" +6. Upload the committed file `db-example.json` to get the schema initialised + +### Getting the service account and API key +1. On the console view (https://console.firebase.google.com/u/1/), click on the Settings cog next to "Project Overview" +2. Click on "Service accounts" +3. Take note of the "Firebase service account" email address, this will be used for `REACT_APP_FB_SRV_ACC_NAME` in `.env` +4. Click on "general" +5. Take note of the "Web API Key", this will be used for `REACT_APP_FB_API_KEY` in `.env`. + +### Setting up basic auth with the Service account user +1. On the console view (https://console.firebase.google.com/u/1/), select "Authentication" and then "Get started" +2. Enable the Email/Password Sign-in method +3. Go to "Users" then click "Add user" +4. Add the service account and a password for it, this password will be used for `REACT_APP_FB_SRV_ACC_PW` in `.env` + +### Protecting the database +1. On the console view (https://console.firebase.google.com/u/1/), select "Authentication" and then "Users" +2. Copy the UUID of your newly added firebase service account (e.g. gSid15y7XJMC8E273OIjLjgaYig2) +3. Go to "Realtime Database" +4. Select "Rules" +5. Change the Rules to only allow reads and writes from the authenticated service account user's UID: + ```aidl + { + "rules": { + ".read": "auth.uid == 'gSid15y7XJMC8E273OIjLjgaYig2'", + ".write": "auth.uid == 'gSid15y7XJMC8E273OIjLjgaYig2'" + } + } + ``` diff --git a/db-example.json b/db-example.json index 437e1cf..bd170c6 100644 --- a/db-example.json +++ b/db-example.json @@ -1,65 +1,30 @@ { "match" : { - "c08c70b5-b487-4085-9787-bbbeb149ab7e" : { - "date" : "2021-05-29T11:27:36.062Z", - "id" : "c08c70b5-b487-4085-9787-bbbeb149ab7e", - "losing_player_id" : "b1494130-3d5a-40f0-84c4-b6bb1e79d861", + "f9c0fcf2-2e7f-48c3-822f-c45f32072abe" : { + "date" : "2021-06-01T03:08:15.037Z", + "id" : "f9c0fcf2-2e7f-48c3-822f-c45f32072abe", + "loser_new_elo" : 1185, + "losing_player_id" : "f0e6e38f-2914-490b-a595-a4567d3266d8", + "losing_player_original_elo" : 1192, "losing_player_score" : 8, - "winning_player_id" : "b68df8d7-cea3-4602-889b-2e1bb169ede4", - "winning_player_score" : 11 - }, - "f47c461c-8d46-406c-9ef3-6cbbc7378d8b" : { - "date" : "2021-05-08T07:56:17.008Z", - "id" : "f47c461c-8d46-406c-9ef3-6cbbc7378d8b", - "losing_player_id" : "b1494130-3d5a-40f0-84c4-b6bb1e79d861", - "losing_player_score" : 8, - "winning_player_id" : "f0e6e38f-2914-490b-a595-a4567d3266d8", - "winning_player_score" : 11 - }, - "f62d6afd-5ea5-4a19-a087-75f39b687e8d" : { - "date" : "2021-05-29T11:34:57.513Z", - "id" : "f62d6afd-5ea5-4a19-a087-75f39b687e8d", - "losing_player_id" : "d68da6b9-8689-491c-b47d-3912090a8f73", - "losing_player_score" : 10, - "winning_player_id" : "b1494130-3d5a-40f0-84c4-b6bb1e79d861", + "winner_new_elo" : 1209, + "winning_player_id" : "f0e6e38f-2914-490b-a595-a4567d3266d7", + "winning_player_original_elo" : 1202, "winning_player_score" : 11 } }, "player" : { - "49861f52-55fb-4d3e-995e-dd9b026778e1" : { - "icon" : "american sign language interpreting", - "id" : "49861f52-55fb-4d3e-995e-dd9b026778e1", - "name" : "LukeS" - }, - "a0dd8cdc-cbd0-403a-ab87-7d4e08aba236" : { - "icon" : "blind", - "id" : "a0dd8cdc-cbd0-403a-ab87-7d4e08aba236", - "name" : "Linc" - }, - "b1494130-3d5a-40f0-84c4-b6bb1e79d861" : { - "icon" : "chess knight", - "id" : "b1494130-3d5a-40f0-84c4-b6bb1e79d861", - "name" : "Swizz" - }, - "b68df8d7-cea3-4602-889b-2e1bb169ede4" : { - "icon" : "playstation", - "id" : "b68df8d7-cea3-4602-889b-2e1bb169ede4", - "name" : "json" - }, - "d68da6b9-8689-491c-b47d-3912090a8f73" : { - "icon" : "eye slash", - "id" : "d68da6b9-8689-491c-b47d-3912090a8f73", - "name" : "Frank" - }, - "d9884fe9-cfd1-4d68-a586-eeeefa7986d9" : { - "icon" : "fire", - "id" : "d9884fe9-cfd1-4d68-a586-eeeefa7986d9", - "name" : "Needham" - }, "f0e6e38f-2914-490b-a595-a4567d3266d8" : { + "elo" : 1185, "icon" : "github alternate", "id" : "f0e6e38f-2914-490b-a595-a4567d3266d8", - "name" : "jjg" + "name" : "test" + }, + "f0e6e38f-2914-490b-a595-a4567d3266d7" : { + "elo" : 1209, + "icon" : "blind", + "id" : "f0e6e38f-2914-490b-a595-a4567d3266d7", + "name" : "test 2" } } -} +} \ No newline at end of file diff --git a/src/api/QuickHitAPI.ts b/src/api/QuickHitAPI.ts index b4a33f5..71609c6 100644 --- a/src/api/QuickHitAPI.ts +++ b/src/api/QuickHitAPI.ts @@ -3,6 +3,7 @@ import {ApiActions, HttpMethod} from "./ApiTypes"; import axios, {AxiosError, AxiosPromise, AxiosResponse} from "axios"; const FB_URL = process.env.REACT_APP_FB_URL; +const FB_API_KEY = process.env.REACT_APP_FB_API_KEY; export class QuickHitAPI { public static getPlayers(onSuccess: (players: DB_Player[]) => void, onFailure: (errorString: string) => void): void { @@ -44,11 +45,28 @@ export class QuickHitAPI { } private static makeAxiosRequest(uri: string, method: HttpMethod, data?: any): AxiosPromise { + return this.authenticateToFirebase().then(response => { + const idToken = response.data.idToken; + + return axios({ + method: method, + baseURL: FB_URL, + url: `${uri}?auth=${idToken}`, + data: data, + headers: {'Content-Type': 'application/json'} + }); + }); + } + + private static authenticateToFirebase(): AxiosPromise { return axios({ - method: method, - baseURL: FB_URL, - url: uri, - data: data, + method: HttpMethod.POST, + baseURL: `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FB_API_KEY}`, + data: { + "email": process.env.REACT_APP_FB_SRV_ACC_NAME, + "password": process.env.REACT_APP_FB_SRV_ACC_PW, + "returnSecureToken": true + }, headers: {'Content-Type': 'application/json'} }); }