Skip to content

Commit 1f4e8f1

Browse files
authored
Merge pull request #54 from ciatph/docker-fixing
docker fixes and updates
2 parents acca26f + b4445a2 commit 1f4e8f1

17 files changed

Lines changed: 240 additions & 54 deletions

File tree

README.md

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,15 @@ A basic web app client in the **/client** directory will show basic API usage an
4444
cd server
4545
npm install
4646
```
47-
3. Set up the environment variables. Create a `.env` file inside the **/server** directory with reference to the `.env.example` file. Encode your own Firebase project settings on the following variables:
48-
- `FIREBASE_SERVICE_ACC`
49-
- The project's private key file contents, condensed into one line and minus all whitespace characters.
50-
- The service account JSON file is generated from the Firebase project's **Project Settings** page, on
51-
**Project Settings** -> **Service accounts** -> **Generate new private key**
52-
- `FIREBASE_PRIVATE_KEY`
53-
- The `private_key` entry from the service account JSON file
54-
- > **NOTE:** Take note to make sure that the value starts and ends with a double-quote on WINDOWS OS localhost. Some systems may or may not require the double-quotes (i.e., Ubuntu running on heroku).
55-
- `ALLOWED_ORIGINS`
56-
- IP/domain origins in comma-separated values that are allowed to access the API
57-
- Include `http://localhost:3000` by default to allow CORS access to the `/client` app.
58-
- `EMAIL_WHITELIST`
59-
- Comma-separated email addresses linked to Firebase Auth UserRecords that are not allowed to be deleted or updated (write-protected)
47+
3. Set up the environment variables. Create a `.env` file inside the **/server** directory with reference to the `.env.example` file. Encode your own Firebase project settings on the following variables:
48+
49+
| Variable Name | Description |
50+
| --- | --- |
51+
|FIREBASE_SERVICE_ACC| The project's private key file contents, condensed into one line and minus all whitespace characters.<br><br>The service account JSON file is generated from the Firebase project's **Project Settings** page, on **Project Settings** -> **Service accounts** -> **Generate new private key**|
52+
|FIREBASE_PRIVATE_KEY| The `private_key` entry from the service account JSON file.<br> <blockquote> **NOTE:** Take note to make sure that the value starts and ends with a double-quote on WINDOWS OS localhost. Some systems may or may not require the double-quotes (i.e., Ubuntu running on heroku).</blockquote> |
53+
|ALLOWED_ORIGINS|IP/domain origins in comma-separated values that are allowed to access the API. Include `http://localhost:3000` by default to allow CORS access to the `/client` app.|
54+
|EMAIL_WHITELIST| Comma-separated email addresses linked to Firebase Auth UserRecords that are not allowed to be deleted or updated (write-protected)<br><br>Default value is `[email protected]`|
55+
|ALLOW_CORS|Allow Cross-Origin Resource Sharing (CORS) on the API endpoints.<br><br>Default value is `1`. Setting to `0` will make all endpoints accept requests from all domains, including Postman.|
6056

6157
### client
6258

@@ -65,12 +61,15 @@ A basic web app client in the **/client** directory will show basic API usage an
6561
cd client
6662
npm install
6763
```
68-
2. Replace `/client/utils/firebase/firebase.config.js` with your own Firebase project's web SDK setup configuration file.
64+
2. Replace the `/client/utils/firebase/firebase.config.js` file with your own Firebase project's web SDK setup configuration file.
6965
- You can find this file in a Firebase project's
7066
**Project Settings** -> **General** -> **Web apps** (Add an app if needed) -> **SDK setup and configuration**
71-
3. Create a `/client/.env` file from the `/client/.env.example` file.
72-
- The `firebase.config.js` settings must match with the `FIREBASE_SERVICE_ACC` environment variable provided on **server - step # 3.**
73-
- Replace `REACT_APP_BASE_URL` with the domain on which the CRUD API is running (default value is `http://localhost:3001/api` on localhost. See the [server](#server) set-up instructions for more information).
67+
- The `firebase.config.js` settings must match with the `FIREBASE_SERVICE_ACC` environment variable defined on **server - step # 3.**
68+
3. Create a `/client/.env` file from the `/client/.env.example` file. Replace the `REACT_APP_BASE_URL` variable with an appropriate value.
69+
70+
| Variable Name | Description |
71+
| --- | --- |
72+
|REACT_APP_BASE_URL|Domain on which the CRUD API is running.<br><br> Default value is `http://localhost:3001/api` on localhost. See the [server](#server) set-up instructions for more information.|
7473
4. Run the app in development mode.
7574
`npm start`
7675
5. Launch the client app in:
@@ -153,38 +152,42 @@ We can use Docker to run dockerized **client** and **server** apps for local dev
153152
- > **INFO:** Building the images for localhost development takes a while, around ~7min+.
154153
4. Create and start the client and server containers.
155154
`docker-compose -f docker-compose-dev.yml up`
156-
5. Launch the dockerized (dev) client app on
155+
5. Run a script in the container to create the default `[email protected]` account, if it does not yet exist in the Firestore database.
156+
`docker exec -it server-prod npm run seed`
157+
6. Launch the dockerized (dev) client app on
157158
`http://localhost:3000`
158-
6. Launch the dockerized (dev) server app's API documentation on
159+
7. Launch the dockerized (dev) server app's API documentation on
159160
`http://localhost:3001/docs`
160-
7. Edit source the codes in `/client/src` or `/server/src` as needed. Verify that hot reload is working on both the client and server apps.
161-
8. Stop and remove containers, networks, images and volumes:
161+
8. Edit source the codes in `/client/src` or `/server/src` as needed. Verify that hot reload is working on both the client and server apps.
162+
9. Stop and remove containers, networks, images and volumes:
162163
`docker-compose -f docker-compose-dev.yml down`
163164

164165
### Docker for Production Deployment
165166

166-
The following docker-compose commands build small client and server images targeted for creating optimized dockerized apps running on production servers. Hot reload is not available when editing source codes from `/client/src` or `/server/src`.
167+
The following docker-compose commands build small client and server images targeted for creating optimized dockerized apps running on self-managed production servers. Hot reload is not available when editing source codes from `/client/src` or `/server/src`.
167168

168-
1. Install and set up the required environment variables as with the required variables on **Docker for Localhost Development**.
169+
1. Install and set up the required **client** and **server** environment variables as with the required variables on [**Docker for Localhost Development**](#docker-for-localhost-development).
169170
2. Build the client and server docker services for production deployment.
170171
- `docker-compose -f docker-compose-prod.yml build`
171172
3. At this point, we can opt to push the docker images to a docker registry of your choice. (Requires sign-in to the selected docker registry).
172173
- `docker-compose -f docker-compose-prod.yml push`
173174
4. Create and start the client and server containers.
174175
`docker-compose -f docker-compose-prod.yml up`
175-
5. Launch the dockerized (prod) client app on
176+
5. Run a script in the container to create the default `[email protected]` account, if it does not yet exist in the Firestore database.
177+
`docker exec -it server-prod npm run seed`
178+
6. Launch the dockerized (prod) client app on
176179
`http://localhost:3000`
177-
6. Launch the dockerized (prod) server app's API documentation on
180+
7. Launch the dockerized (prod) server app's API documentation on
178181
`http://localhost:3001/docs`
179-
7. Stop and remove containers, networks, images and volumes:
182+
8. Stop and remove containers, networks, images and volumes:
180183
`docker-compose -f docker-compose-prod.yml down`
181184

182185
## Pre-built Server Docker Image
183186

184-
**firebase-users-admin**'s `server` component is available as a stand-alone docker image on Docker Hub.
187+
**firebase-users-admin**'s `server` component is available as a stand-alone docker image on Docker Hub with customizable environment variables (.env file).
185188

186189
1. Pull the (production) **/server** docker image from Docker Hub.
187-
`docker pull ciatphdev/firebase-users-admin-server:v1.1.0`
190+
`docker pull ciatphdev/firebase-users-admin-server:v1.1.1`
188191
2. Create a `.env` file.
189192
- Read [**Installation - server #3**](#server) for more information.
190193
- Replace the variables accordingly in the `.env` file.
@@ -193,15 +196,18 @@ The following docker-compose commands build small client and server images targe
193196
FIREBASE_SERVICE_ACC=YOUR-FIREBASE-PROJ-SERVICE-ACCOUNT-JSON-CREDENTIALS-ONE-LINER-NO-SPACES
194197
FIREBASE_PRIVATE_KEY=PRIVATE-KEY-FROM-FIREBASE-SERVICE-ACCOUNT-JSON-WITH-DOUBLE-QUOTES
195198
199+
ALLOW_CORS=1
196200
```
197201
3. Run the image.
198202
```
199203
docker run -it --rm \
200204
--env-file .env
201205
-p 3001:3001 \
202-
ciatphdev/firebase-users-admin-server:v1.1.0
206+
ciatphdev/firebase-users-admin-server:v1.1.1
203207
```
204-
4. Launch the server API documentation on
208+
4. Run a script in the container to create the default `[email protected]` account, if it does not yet exist in the Firestore database.
209+
`docker exec -it server-prod npm run seed`
210+
5. Launch the server API documentation on
205211
`http://localhost:3001/docs`
206212
207213

client/nginx/nginx.conf

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1+
# Minimal nginx configuration for running locally in containers
12
server {
23
listen 3000;
4+
5+
root /usr/share/nginx/html;
6+
include /etc/nginx/mime.types;
7+
index index.html index.html;
8+
39
server_name localhost;
10+
server_tokens off;
411

5-
location /api {
6-
proxy_pass http://server-prod:3001;
12+
# Rewrite all React URLs/routes to index.html
13+
location / {
14+
try_files $uri $uri/ /index.html =404;
715
}
816

9-
location / {
10-
root /usr/share/nginx/html;
11-
index index.html index.html
12-
try_files $uri /index.html;
17+
# Reverse proxy to the backend API server
18+
# Requires the backend service running on a container named 'server-prod'
19+
location /api {
20+
proxy_pass http://server-prod:3001;
21+
proxy_set_header Host $host;
1322
}
1423

24+
# Other error pages
1525
error_page 500 502 503 504 /50x.html;
1626
location = /50x.html {
1727
root /usr/share/nginx/html;

client/nginx/nginx.full.conf

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Full nginx configuration with SSL certificate for nginx running on host machine
2+
# Requires a registered domain name, letsencrypt SSL certificates
3+
# and local client/server apps (running in containers or manually installed on host)
4+
5+
server {
6+
listen 80;
7+
listen [::]:80;
8+
server_name www.<YOUR.DOMAIN.COM.HERE>;
9+
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
10+
}
11+
12+
server {
13+
listen 80;
14+
listen [::]:80;
15+
server_name <YOUR.DOMAIN.COM.HERE>;
16+
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
17+
}
18+
19+
server {
20+
listen 443 ssl;
21+
server_name www.<YOUR.DOMAIN.COM.HERE>;
22+
ssl_certificate /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/fullchain.pem;
23+
ssl_certificate_key /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/privkey.pem;
24+
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
25+
}
26+
27+
server {
28+
listen 443 ssl http2;
29+
listen [::]:443 ssl http2;
30+
31+
server_name <YOUR.DOMAIN.COM.HERE>;
32+
server_tokens off;
33+
34+
# Available methods
35+
add_header Allow 'GET, POST, PATCH, DELETE, HEAD' always;
36+
add_header X-XSS-Protection '1; mode=block';
37+
38+
if ( $request_method !~ ^(GET|POST|PATCH|DELETE|HEAD)$ ) {
39+
return 405;
40+
}
41+
42+
ssl_certificate /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/fullchain.pem;
43+
ssl_certificate_key /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/privkey.pem;
44+
45+
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
46+
ssl_prefer_server_ciphers on;
47+
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
48+
ssl_dhparam '/etc/pki/nginx/dhparams.pem';
49+
50+
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains' always;
51+
52+
# gzip comppression settings
53+
gzip on;
54+
gzip_disable 'msie6';
55+
56+
gzip_vary on;
57+
gzip_proxied any;
58+
gzip_comp_level 6;
59+
gzip_buffers 16 8k;
60+
gzip_http_version 1.1;
61+
gzip_min_length 0;
62+
gzip_types text/plain application/javascript text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype;
63+
64+
# Reverse proxy to the client website
65+
# Requires the client service running on http://localhost:3000 (from a container or manually installed on host)
66+
location / {
67+
proxy_pass http://localhost:3000;
68+
proxy_set_header Host $host;
69+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
70+
proxy_set_header X-Forwarded-Proto $scheme;
71+
proxy_set_header X-Real-IP $remote_addr;
72+
proxy_cache_bypass $http_upgrade;
73+
74+
# For websockets
75+
proxy_http_version 1.1;
76+
proxy_set_header Connection 'upgrade';
77+
proxy_set_header Upgrade $http_upgrade;
78+
proxy_read_timeout 600s;
79+
}
80+
81+
# Reverse proxy to the backend API server
82+
# Requires the backend service running on http://localhost:3001 (from a container or manually installed on host)
83+
location /api {
84+
proxy_pass http://localhost:3001;
85+
proxy_set_header Host $host;
86+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
87+
proxy_set_header X-Forwarded-Proto $scheme;
88+
proxy_set_header X-Real-IP $remote_addr;
89+
proxy_cache_bypass $http_upgrade;
90+
91+
# For websockets
92+
proxy_http_version 1.1;
93+
proxy_set_header Connection 'upgrade';
94+
proxy_set_header Upgrade $http_upgrade;
95+
proxy_read_timeout 600s;
96+
}
97+
98+
# Other error pages
99+
error_page 500 502 503 504 /50x.html;
100+
location = /50x.html {
101+
root /usr/share/nginx/html;
102+
}
103+
}

client/src/components/404/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function NotFound () {
2+
return (
3+
<h2>Page not found</h2>
4+
)
5+
}
6+
7+
export default NotFound

client/src/components/common/userform/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ function UserForm (props) {
5151
onChange={onTextChange}
5252
/>
5353

54+
<TextField
55+
id='password'
56+
label='Enter password'
57+
variant='outlined'
58+
size='small'
59+
disabled={loadstatus.isLoading}
60+
value={state.password}
61+
onChange={onTextChange}
62+
/>
63+
5464
<InputLabel sx={styles.formlabel} id='accountlevel-label'>Account Type</InputLabel>
5565
<Select
5666
labelId='accountlevel-label'

client/src/containers/createuser/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createUser } from '../../utils/service'
33
import UserForm from '../../components/common/userform'
44

55
const defaultState = {
6-
email: '', displayname: '', account_level: '1', disabled: false, emailverified: false
6+
email: '', displayname: '', password: '', account_level: '1', disabled: false, emailverified: false
77
}
88

99
const defaultLoadingState = {

client/src/containers/updateuser/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { updateUser } from '../../utils/service'
44
import UserForm from '../../components/common/userform'
55

66
const defaultState = {
7-
email: '', displayname: '', account_level: '1', disabled: false, emailverified: false
7+
email: '', displayname: '', password: '', account_level: '1', disabled: false, emailverified: false
88
}
99

1010
const defaultLoadingState = {

client/src/routes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import DashboardContainer from './containers/dashboard'
22
import LoginContainer from './containers/login'
33
import CreateUserContainer from './containers/createuser'
44
import UpdateUserContainer from './containers/updateuser'
5+
import NotFound from './components/404'
56
import Home from './components/home'
67

78
const routes = [
@@ -29,6 +30,11 @@ const routes = [
2930
path: '/',
3031
isProtected: false,
3132
component: Home
33+
},
34+
{
35+
path: '*',
36+
isProtected: false,
37+
component: NotFound
3238
}
3339
]
3440

client/src/utils/service/service.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ export default class Service {
5050
}
5151

5252
async createUser (user) {
53-
const fields = ['email', 'displayname', 'account_level', 'disabled', 'emailverified']
53+
const fields = ['email', 'displayname', 'password', 'account_level', 'disabled', 'emailverified']
5454
const body = {}
5555

5656
fields.forEach((item) => {
57-
if (user[item] !== undefined) {
57+
if (user[item] !== undefined && user[item] !== '') {
5858
body[item] = user[item]
59+
} else {
60+
throw new Error('Please check your input.')
5961
}
6062
})
6163

@@ -65,12 +67,14 @@ export default class Service {
6567
}
6668

6769
async updateUser (info) {
68-
const fields = ['uid', 'email', 'displayname', 'disabled', 'emailverified', 'account_level']
70+
const fields = ['uid', 'email', 'displayname', 'password', 'disabled', 'emailverified', 'account_level']
6971
const body = {}
7072

7173
fields.forEach((item) => {
72-
if (info[item.toLowerCase()] !== undefined) {
74+
if (info[item.toLowerCase()] !== undefined && info[item.toLowerCase()] !== '') {
7375
body[item] = info[item.toLowerCase()]
76+
} else {
77+
throw new Error('Please check your input.')
7478
}
7579
})
7680

docker-compose.dev.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
version: "2.6.0"
1+
version: "3"
22
services:
3+
# Create React App (CRA) running on development mode
34
client-dev:
45
container_name: client-dev
56
image: ciatphdev/firebase-users-admin-client:dev
@@ -15,6 +16,7 @@ services:
1516
ports:
1617
- "3000:3000"
1718

19+
# Express app running in development mode with auto reload using nodemon
1820
server-dev:
1921
container_name: server-dev
2022
image: ciatphdev/firebase-users-admin-server:dev

0 commit comments

Comments
 (0)