"Crack Kraków with the Cracker!"
Big thanks to zgm92 for collaboration. 👏
Big thanks to ASchabowska for debugging and dev-ops help. 👏
Big thanks to thomsa and barlima for time and advice. 👏
- Setup
- Snippets
- Visual Studio Code extensions
- Resources
- Log in or create new account on Auth0 website.
- Go to
Applications
and clickCreate application
. - Name it
Cracker
, selectSingle Page Web Applications
and clickCreate
. - Go to
Settings
tab where you can find:- Domain (required in
cracker-client
asREACT_APP_AUTH0_CLIENT_ID
) - Client ID (required in
cracker-client
asREACT_APP_AUTH0_DOMAIN
andcracker-server
asAUTH0_DOMAIN
)
- Domain (required in
- Please remember to set the callback URLs in Auth0 (URLs should be separated with a comma):
- Allowed Callback URLs:
https://localhost:3000/callback
- running app fully locallyhttps://the.ip.of.docker.machine/callback
- running app as Docker production build
- Allowed Logout URLs, Allowed Web Origins and Allowed Origins (CORS):
https://localhost:3000/
- running app fully locallyhttps://the.ip.of.docker.machine/
- running app as Docker production build
- Allowed Callback URLs:
- Go to
APIs
and clickCreate API
. - Write
Cracker API
in theName
field. - Write
https://cracker.app
in theIdentifier
field (it has to be in the HTTP format) and clickCreate
. - The
Identifier
value should be set in.env
files underAUDIENCE
forcracker-server
andREACT_APP_AUDIENCE
forcracker-client
. - Go to
Settings
section of the newly created API and scroll toRBAC Settings
paragraph. - Make the
Enable RBAC
switch enabled and after that also enable theAdd Permissions in the Access Token
switch. - Scroll to the bottom and click
Save
. - Go to
Permissions
and find theAdd a Permission (Scope)
section. - Add these permissions (scopes):
Permission (Scope) | Description |
---|---|
update:markers |
Update markers |
delete:markers |
Delete markers |
create:markers |
Create markers |
Read option is by available for everyone by default.
- Open
Users & Roles
section in the main menu and got toRoles
. - Click
+ Create roles
fill theName
asadmin
,Description
asCracker app administrator
and clickCreate
. - The new role should be visible in the table - go to its details/settings by clicking the role
admin
name. - Go to
Permissions
section and clickAdd permissions
. - Select the
Cracker API
(https://cracker.red
API defined earlier) and either clickAll
next toSelect all:
sign, or manually check all the available scopes. - Click
Add permissions
.
You can try to define other scopes. No other user roles other than admin
are currently used. You select the account and then navigate to Permissions
part.
- Open
Users & Roles
section in the main menu and got toRoles
. - Go to details/settings of
admin
role by clicking its name. - Go to
Users
section and clickAdd users
. - In the
Select users
dropdown start typing the email address of the account you want to assign admin role. - After selecting the account, click
Assign
.
You can also do this using the Roles
section in the Users & Roles
main menu submenu.
Because there is no easy way to deal with getting the user role or permissions from id token provided by Auth0, we need to write a custom rule which will automatically attach that information to the token. Parsing access tokens on client side for any reason is strongly discouraged due to its backend character, that is why we can't get permissions by just reading them from a decoded access token.
- Go to
Auth0
and selectRules
from the menu and click+ Create rule
. - Pick an
</> Empty rule
template. - Change the name to
Add Cracker roles to token
and fill theScript
part with code from below:
function (user, context, callback) {
var map = require('array-map');
var ManagementClient = require('auth0@2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
var params = { id: user.user_id, page: 0, per_page: 50, include_totals: true };
management.getUserPermissions(params, function (err, permissions) {
if (err) {
console.log('err: ', err);
callback(err);
} else {
var permissionsArr = map(permissions.permissions, function (permission) {
return permission.permission_name;
});
context.idToken['https://www.crackerapp.com'] = {
permissions: permissionsArr
};
}
callback(null, user, context);
});
}
The https://
namespaced convention is necessary in Auth0 to avoid overriding default fields.
- After you save, the user access token should have the role property. To verify this try to invoke a request in the browser which will have the
authorization
header with jwt token. Copy the token and verify it on jwt.io.
In production mode, Cracker app uses Nginx to serve the React static files and route traffic to the backend API. This way the HTTPS can be handled in a quite easy way by Nginx itself and that is the point of SSL termination (going from encrypted HTTPS to unecrypted HTTP).
A problem emerges in local development mode where we would want to utilize all the benefits of Webpack hosting React files and providing HTTPS. Because Apollo backend API doesn't have HTTPS defined, the direct calls from React client to Apollo backend would be blocked by the browser due to mixed content (one can't call HTTP endpoint while being hosted with HTTPS). That is why we have an additional container - cracker-proxy
which handles the HTTPS termination for communication with the backend.
- Use this instruction to generate SSL certificates (I have used Windows OpenSSL alternative which is available here - everything is described in the instruction provided previously). Keep the name of the certificate
fullchain.pem
andprivkey.pem
for the private key, for example, using OpenSSL:
openssl req -x509 -newkey rsa:4096 -nodes -keyout fullchain.pem -out privkey.pem -subj “/C=PL/L=Kraków/CN=cracker.red” -days 600
req
- request a certificate-x509
- a standard defining the format of public key certificates-newkey rsa:4096
- a new private key (-newkey
) using the RSA algorithm with a 4096-bit key length (rsa:4096
)-nodes
- private key should be without using a passphrase-keyout
- key filename-out
- certificate filename-subj
- subject - this can have parameters like country (C=PL
), location (L=Poland
), organisation (O=Cracker Ltd
), company name (CN=www.cracker.red
)-days
- how long should the certificate be valid
- After you have generated the SSL certificate, you should have two files with
.pem
extensions. Copy them to./certificates
directory for local development.
The website will fail if the certificates are not present. The nginx
production configuration file expects SSL certificates in a certain directory which is bound to the cracker.red
domain:
ssl_certificate /etc/letsencrypt/live/cracker.red/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cracker.red/privkey.pem;
If you are planning to generte them for your own domain, please change the directory in ./cracker-client/nginx/default.conf
file.
- Go to you Lightsail instance.
- Run
docker run --entrypoint="/bin/sh" -it --name certbot -v /home/ubuntu/certbot/conf:/etc/letsencrypt -v /home/ubuntu/certbot/www:/var/www/certbot certbot/certbot:latest
to get thecertbot
Docker image and start it with theshell
entry point instead of regularcertbot
process. If you remove the--entrypoint
argument you will not have to run the next command. Changing the entry point toshell
is just for control reason, because container would exit just when thecertbot
process ends. - Run
certbot certonly
in thecertbot
container.
- Fill the
Domain name:
with your personal domain data (for exampleyour.domain
). You need a domain to have a proper certification. - Fill the
Input the webroot for your.domain:
with/var/www/certbot
route. This directory is wherecertbot
puts a verification token that is accessible throughnginx
for the Automatic Certificate Management Environment (ACME) challenge. The HTTP route for that is/.well-known/acme-challenge/
(you can read more about this process in HTTP-01 challenge section).
- Run
exit
after the process ends successfully like below:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/your.domain/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/your.domain/privkey.pem
Your cert will expire on 2021-03-23. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew _all_ of your certificates, run
"certbot renew"
The Docker images are prepared in a way that volumes of certbot
are mounted to the same location (/etc/letsencrypt
) as the volumes of cracker-client
, so the generated certificates are instantly available for the website. Remember that certbot
manages the Let's Encrypt
certificates and stores their renewal information in the /etc/letsencrypt
directory. It is not recommended to modify those directories because it might make the certificate renewal process impossible.
After less than 30 days you need to perform the recertification of the domain HTTPS SSL certificate.
- Go to you Lightsail instance.
- Run
docker start -ai certbot
. There is already a container namedcertbot
after the first certification. - Run
certbot renew
. If the output message is as written below, the recertification went OK:
Congratulations, all renewals succeeded. The following certs have been renewed
- After that, we need to restart the
nginx
service. You can achieve this by using one of those methods:- Rebooting the Lightsail instance with
aws lightsail reboot-instance --instance-name Cracker-app
(this will also install updates for your instance, so it's worth executing from time to time ;) ). - Rebooting the container with
docker restart cracker-client
. - Rebooting the
nginx
service inside the Docker containerdocker exec cracker-client nginx -s reload
.
- Rebooting the Lightsail instance with
The development proxy listens on /api
with HTTPS and proxies the traffic with HTTP to port :4000
of Apollo API. The API and the Apollo GQL playground are still available also with a direct :4000
call. For example, to try out the Apollo GraphQL Playground you would just use either http://192.168.99.100:4000/graphql
or https://192.168.99.100/api
(but here you would need to correct the address in the Playground UI to https://192.168.99.100/api
instead of https://192.168.99.100/graphql
).
To try out a call with authorization do this (instruction for Chrome):
- Run the application.
- Log in to any account and go to
Markers
section. - When in
Markers
, pressF12
to accessDevTools
. - Go to
Network
tab and click on anapi
request. - Navigate to
Headers
tab of that request and go toRequest Headers
section. - You should find the
authorization
header there - copy the whole header with its value (authorization: Bearer your.access.token
). - Enter the Playground as described above.
- Open
HTTP HEADERS
section in the Playground, write curly braces and paste the authorization header as in the example below (remember about the quotation marks):
{
"authorization": "Bearer your.access.token"
}
- Paste the GraphQL request:
{
getVersion
}
- Click the run button. You should get a response with API version like below:
{
"data": {
"getVersion": "0.0.1"
}
}
- Create a
.env
file incracker-server
directory:
AUTH0_DOMAIN="Auth0 user domain"
AUDIENCE="http://your.api.identifier"
CORS_WHITELIST="Client origin address"
Example of local development .env
for cracker-server
:
AUTH0_DOMAIN=domain.region.auth0.com
AUDIENCE=https://cracker.app
CORS_WHITELIST="https://example.com https://192.168.99.100"
- Create a
.env
file incracker-client
directory:
REACT_APP_API_URL="address of Apollo GQL backend"
REACT_APP_AUTH0_DOMAIN="Auth0 user domain"
REACT_APP_AUTH0_CLIENT_ID="Auth0 user client ID"
REACT_APP_AUDIENCE="http://your.api.identifier"
Remember that while setting REACT_APP_API_URL
in local development, the client container does not have nginx
- that means that cracker-server
is available as :4000
HTTP and not /api
HTTPS. Apollo GQL Playground should be available after start at :4000
(if you use VirtualBox
, the address can be http://192.168.99.100:4000/
and for regular Docker
development either http://127.0.0.1:4000/
or http://localhost:4000/
).
On the other hand, the :3000
port for Webpack React development is mapped in docker-compose.yml
to standard HTTP :443
HTTPS port, so the app is visible for Auth0 as (for example) https://127.0.0.1/
(go to cracker-client to read more on how to configure custom SSL certificates for HTTPS local development if necessary).
Example of local development .env
for cracker-client
:
REACT_APP_API_URL=http://127.0.0.1:4000
REACT_APP_AUTH0_DOMAIN=domain.region.auth0.com
REACT_APP_AUTH0_CLIENT_ID=i6mdgjdsjs45asdmfdg3453TADasdkaa
REACT_APP_AUDIENCE=https://cracker.app
- Run
npm install
incracker-client
. - Run
npm install
incracker-server
(if you are running this on Windows, there might be a conflict of environments forSharp
package - please read the error section README forcracker-server
). - Run
docker-compose build
. - Run
docker-compose up
.
Please remember that you might not have enough space on your virutal machine and get an fallocate:: No space left on device
error from cracker-db-dev
. Please run docker image prune -a
to free disk space.
Please remember that Lightsail has a firewall that doesn't have HTTPS port 443 open by default. This port is closed every time a new instance is being set up, so it is very important to open it to make the app reachable with https://
address.
You can use the setup.sh
script to setup a new instance on Lightsail autmatically. Please keep this directory as current work directory (invoke the script as ./scripts/setup.sh
). Remember to have the aws cli
installed and logged to your account. Also, you will need to be logged to Docker Hub (images in the script are tagged for my repository - you need to change this manually, for example mjgasior/cracker-server:0.0.1
to youraccount/cracker-server:0.0.1
)
- Remember to set up proper Auth0 (client ID and the domain) values in
cracker-client
andcracker-server
. - Put the SSL certificates for HTTPS next to
nginx
configuration incracker-client/nginx
directory. The names should befullchain.pem
andprivkey.pem
for private key. - Set proper IP address of the API in
.env
file incracker-client
for new Lightsail instance (for exampleREACT_APP_API_URL=https://18.196.197.102/api
). - Set
GENERATE_SOURCEMAP=false
in the.env
file incracker-client
to avoid creating source maps for the generated code (therelease.sh
script adds this automatically, so if you need to investigate the source code, delete this value before creating a release - source maps are downloaded only if the user has React DevTools). - Run
docker-compose -f docker-compose.prod.yml build
- Run
docker-compose -f docker-compose.prod.yml up
- Log yourself in to Docker Hub
docker login
. - Build Docker images.
- Tag them
docker tag 6d15e9c73b54 mjgasior/cracker-client:0.0.3
. - Push them
docker push mjgasior/cracker-client:0.0.3
. - Use them. :)
If you want to release from branch, you can use the ./scripts/release.sh
script:
- Run
.\scripts\release.sh
(Windows slash notation here). - Connect through SSH to Lightsail instance.
- Run
sudo curl -o /srv/docker/docker-compose.yml https://raw.githubusercontent.com/mjgasior/cracker-app/BRANCH_NAME/deploy/docker-compose.yml
. - Go to
/srv/docker
withcd /srv/docker
. - Run
docker-compose down
and thendocker-compose up
.
It might be necessary to run a manual installation of sharp
after a release to run a local development again:
npm install --arch=x64 --platform=linuxmusl --target=8.10.0 sharp
cat filename
- display the contents of a text file in the command linecurl -X POST https://example.com/resource.cgi
- cURL a POST requestdocker exec -it container_id_or_name ash
- starting shell in the Docker Alpine container (Alpine doesn't have bash by default)docker system prune -a
- remove all stopped containers, all dangling images, and all unused networksdocker rmi $(docker images -a -q)
- remove all images, the -q flag is used to pass the Image IDdocker run -it -p 3000:3000 -e CHOKIDAR_USEPOLLING=true -v $(pwd):/var/www -w "/var/www" node:12.0-alpine yarn start
exit
- to exit out of the docker container bash shell just run thisgit branch | %{ $_.Trim() } | ?{ $_ -ne 'master' } | %{ git branch -D $_ }
- delete all branches except masterhistory
allows you to see thesh
commands history - to run a certain command just write!
and the number of the command you would want to rerun, for example!123
rc-service --list | grep -i nginx
- list all the services and filter searching fornginx
stat --format '%a' <file>
- get the chmod numerical value for a file
- Docker - extension makes it easy to build, manage and deploy containerized applications from Visual Studio Code
- GitLens - Git supercharged - adds the Git capabilities into Visual Studio Code, helps to visualize code authorship, navigate and explore Git repositories
- Adding custom claims in Auth0
- Dockerizing a React App
- Docker tips
- Dropping Mongo database manually
- Dropping Mongo database with a shell script
- How to create SSL certificates for development
- LICEcap for simple animated screen captures
- Managing MongoDB on docker with docker-compose
- Nginx and Let’s Encrypt with Docker in Less Than 5 Minutes
- Unable to start Docker MongoDB image on Windows with a volume