diff --git a/README.md b/README.md index 27f770139d..ebf9486783 100644 --- a/README.md +++ b/README.md @@ -4,48 +4,47 @@ Accelerate your next web development project with this FastAPI/Nuxt.js base project generator. -This project is a comprehensively updated fork of [Sebastián Ramírez's](https://github.com/tiangolo) [Full Stack FastAPI and PostgreSQL Base Project Generator](https://github.com/tiangolo/full-stack-fastapi-postgresql). FastAPI is updated to version 0.88 (November 2022), SQLAlchemy to version 2.0 (January 2023), and the frontend to Nuxt 3.2 (February 2023). +This project is for developers looking to build and maintain full-feature progressive web applications using Python on the backend / Typescript on the frontend, and want the complex-but-routine aspects of auth 'n auth, and component and deployment configuration, taken care of, including interactive API documentation. -Generate a backend and frontend stack using Python, including interactive API documentation. +This is a comprehensively updated fork of [Sebastián Ramírez's](https://github.com/tiangolo) [Full Stack FastAPI and PostgreSQL Base Project Generator](https://github.com/tiangolo/full-stack-fastapi-postgresql). FastAPI is updated to version 0.88 (November 2022), SQLAlchemy to version 2.0 (January 2023), and the frontend to Nuxt 3.2 (February 2023). - [Screenshots](#screenshots) - [Key features](#key-features) - [How to use it](#how-to-use-it) - - [Generate passwords](#generate-passwords) - - [Input variables](#input-variables) - - [Local development](#local-development) - - [Starting Jupyter Lab](#starting-jupyter-lab) -- [How to deploy](#how-to-deploy) -- [Authentication with magic and TOTP](#authentication-with-magic-and-totp) + - [Getting started](getting-started.md) + - [Development and installation](#development-and-installation) + - [Deployment for production](#deployment-for-production) + - [Authentication and magic tokens](#authentication-and-magic-tokens) - [More details](#more-details) - [Release notes](#release-notes) - [License](#license) - + ## Screenshots ### App landing page -[![Landing page](img/landing.png)](https://github.com/whythawk/full-stack-fastapi-postgresql) +![Landing page](img/landing.png) ### Dashboard Login -[![Magic-link login](img/login.png)](https://github.com/whythawk/full-stack-fastapi-postgresql) +![Magic-link login](img/login.png) ### Dashboard User Management -[![Moderator user management](img/dashboard.png)](https://github.com/whythawk/full-stack-fastapi-postgresql) +![Moderator user management](img/dashboard.png) ### Interactive API documentation -[![Interactive API docs](img/redoc.png)](https://github.com/whythawk/full-stack-fastapi-postgresql) +![Interactive API docs](img/redoc.png) ### Enabling two-factor security (TOTP) -[![Enabling TOTP](img/totp.png)](https://github.com/whythawk/full-stack-fastapi-postgresql) - +![Enabling TOTP](img/totp.png) ## Key features +This FastAPI, PostgreSQL, Neo4j & Nuxt 3 repo will generate a complete web application stack as a foundation for your project development. + - **Docker Compose** integration and optimization for local development. - **Authentication** user management schemas, models, crud and apis already built, with OAuth2 JWT token support & default hashing. Offers _magic link_ authentication, with password fallback, with cookie management, including `access` and `refresh` tokens. - [**FastAPI**](https://github.com/tiangolo/fastapi) backend with [Inboard](https://inboard.bws.bio/) one-repo Docker images: @@ -72,142 +71,10 @@ Generate a backend and frontend stack using Python, including interactive API do ## How to use it -Go to the directory where you want to create your project and run: - -```bash -pip install cookiecutter -cookiecutter https://github.com/whythawk/full-stack-fastapi-postgresql -``` - -### Generate passwords - -You will be asked to provide passwords and secret keys for several components. Open another terminal and run: - -```bash -openssl rand -hex 32 -# Outputs something like: 99d3b1f01aa639e4a76f4fc281fc834747a543720ba4c8a8648ba755aef9be7f -``` - -Copy the contents and use that as password / secret key. And run that again to generate another secure key. - - -### Input variables - -The generator (cookiecutter) will ask you for some data, you might want to have at hand before generating the project. - -The input variables, with their default values (some auto generated) are: - -- `project_name`: The name of the project -- `project_slug`: The development friendly name of the project. By default, based on the project name -- `domain_main`: The domain in where to deploy the project for production (from the branch `production`), used by the load balancer, backend, etc. By default, based on the project slug. -- `domain_staging`: The domain in where to deploy while staging (before production) (from the branch `master`). By default, based on the main domain. -- `domain_base_api_url`: The domain url used by the frontend app for backend api calls. If deploying a localhost development environment, likely to be `http://localhost/api/v1` -- `domain_base_ws_url`: The domain url used by the frontend app for backend websocket calls. If deploying a localhost development environment, likely to be `ws://localhost/api/v1` - -- `docker_swarm_stack_name_main`: The name of the stack while deploying to Docker in Swarm mode for production. By default, based on the domain. -- `docker_swarm_stack_name_staging`: The name of the stack while deploying to Docker in Swarm mode for staging. By default, based on the domain. - -- `secret_key`: Backend server secret key. Use the method above to generate it. -- `totp_secret_key`: Two-factor security (TOTP) server secret key. -- `first_superuser`: The first superuser generated, with it you will be able to create more users, etc. By default, based on the domain. -- `first_superuser_password`: First superuser password. Use the method above to generate it. -- `backend_cors_origins`: Origins (domains, more or less) that are enabled for CORS (Cross Origin Resource Sharing). This allows a frontend in one domain (e.g. `https://dashboard.example.com`) to communicate with this backend, that could be living in another domain (e.g. `https://api.example.com`). It can also be used to allow your local frontend (with a custom `hosts` domain mapping, as described in the project's `README.md`) that could be living in `http://dev.example.com:8080` to communicate with the backend at `https://stag.example.com`. Notice the `http` vs `https` and the `dev.` prefix for local development vs the "staging" `stag.` prefix. By default, it includes origins for production, staging and development, with ports commonly used during local development by several popular frontend frameworks (Vue with `:8080`, React, Angular). -- `smtp_tls`: Transport Sockets Layer (or Secure Sockets Layer) boolean setting. By default `True`. -- `smtp_port`: Port to use to send emails via SMTP. By default `587`. -- `smtp_host`: Host to use to send emails, it would be given by your email provider, like Mailgun, Sparkpost, etc. -- `smtp_user`: The user to use in the SMTP connection. The value will be given by your email provider. -- `smtp_password`: The password to be used in the SMTP connection. The value will be given by the email provider. -- `smtp_emails_from_email`: The email account to use as the sender in the notification emails, it could be something like `info@your-custom-domain.com`. -- `smtp_emails_from_name`: The email account name to use as the sender in the notification emails, it could be something like `Symona Adaro`. -- `smtp_emails_to_email`: The email account to use as the recipient for `contact us` emails, it could be something like `requests@your-custom-domain.com`. - -- `postgres_password`: Postgres database password. Use the method above to generate it. (You could easily modify it to use MySQL, MariaDB, etc). -- `pgadmin_default_user`: PGAdmin default user, to log-in to the PGAdmin interface. -- `pgadmin_default_user_password`: PGAdmin default user password. Generate it with the method above. - -- `neo4j_password`: Neo4j database password. Use the method above to generate it. - -- `traefik_constraint_tag`: The tag to be used by the internal Traefik load balancer (for example, to divide requests between backend and frontend) for production. Used to separate this stack from any other stack you might have. This should identify each stack in each environment (production, staging, etc). -- `traefik_constraint_tag_staging`: The Traefik tag to be used while on staging. -- `traefik_public_constraint_tag`: The tag that should be used by stack services that should communicate with the public. - -- `flower_auth`: Basic HTTP authentication for flower, in the form`user:password`. By default: "`admin:changethis`". - -- `sentry_dsn`: Key URL (DSN) of Sentry, for live error reporting. You can use the open source version or a free account. E.g.: `https://1234abcd:5678ef@sentry.example.com/30`. - -- `docker_image_prefix`: Prefix to use for Docker image names. If you are using GitLab Docker registry it would be based on your code repository. E.g.: `git.example.com/development-team/my-awesome-project/`. -- `docker_image_backend`: Docker image name for the backend. By default, it will be based on your Docker image prefix, e.g.: `git.example.com/development-team/my-awesome-project/backend`. And depending on your environment, a different tag will be appended ( `prod`, `stag`, `branch` ). So, the final image names used will be like: `git.example.com/development-team/my-awesome-project/backend:prod`. -- `docker_image_celeryworker`: Docker image for the celery worker. By default, based on your Docker image prefix. -- `docker_image_frontend`: Docker image for the frontend. By default, based on your Docker image prefix. - -### Local development - -Once the Cookiecutter script has completed, you will have a folder populated with the base project and all input variables customised. - -Change into the project folder and run the `docker-compose` script to build the project containers: - -```bash -docker-compose build --no-cache -``` - -And start them: - -```bash -docker-compose up -d -``` - -**NOTE:** If you install new Node packages, you will need to rebuild the `frontend`. I also find that `frontend` behaves inconsistently in development mode, and may not refresh on changes. In particular, `nuxt/content` is very unpredictable in dev mode running in the container. You may have more success running the `frontend` outside of the container to take advantage of live refresh. - -Change into the `/frontend` folder, and: - -```bash -yarn install -yarn dev -``` - -Be careful about the version of `Node.js` you're using. As of today (December 2022), the latest Node version supported by Nuxt is 16.18.1. - -You can then view the frontend at `http://localhost:3000` and the backend api endpoints at `http://localhost/redoc`. - -FastAPI `backend` updates will refresh automatically, but the `celeryworker` container must be restarted before changes take effect. - -### Starting Jupyter Lab - -If you like to do algorithmic development and testing in Jupyter Notebooks, then launch the `backend` terminal and start Jupyter as follows: - -```bash -docker-compose exec backend bash -``` - -From the terminal: - -```bash -$JUPYTER -``` - -Copy the link generated into your browser and start. - -**NOTE:** Notebooks developed in the container are not saved outside, so remember to copy them for persistence. You can do that from inside Jupyter (download), or: - -```bash -docker cp :/file/path/within/container /host/path/target -``` - -## How to deploy - -This stack can be adjusted and used with several deployment options that are compatible with Docker Compose, but it is designed to be used in a cluster controlled with pure Docker in Swarm Mode with a Traefik main load balancer proxy handling automatic HTTPS certificates, using the ideas from DockerSwarm.rocks. - -Please refer to DockerSwarm.rocks to see how to deploy such a cluster in 20 minutes. - -## Authentication with magic and TOTP - -> Any custodial changes to user-controlled information must be treated as requiring full authentication. Do **not** assume that a logged-in user is the authorised account holder. - -Most web applications permit account recovery through requesting a password reset via email. This has process has been hardened using dual JWT tokens, and is offered as a primary authentication method, with password fallback. - -Time-based One-Time Password (TOTP) authentication extends the login process to include a challenge-response component where the user needs to enter a time-based token after their preferred login method. - -For development, you may prefer to use login and password. +- [Getting started](getting-started.md) +- [Development and installation](development-guide.md) +- [Deployment for production](deployment-guide.md) +- [Authentication and magic tokens](authentication-guide.md) ## More details @@ -220,7 +87,7 @@ See notes and [releases](https://github.com/whythawk/full-stack-fastapi-postgres ### 0.7.3 - @nuxt/content 2.2.1 -> 2.4.3 - Fixed: `@nuxt/content` default api, `/api/_content`, conflicts with the `backend` api url preventing content pages loading. -- Documentation: Complete deployment guide in `DEPLOYMENT-README.md` +- Documentation: Complete deployment guide in `DEPLOYMENT-README.md` (this has now been moved to `/docs`) ### 0.7.2 - Fixed: URLs for recreating project in generated `README.md`. PR [#15](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/15) by @FranzForstmayr diff --git a/docs/authentication-guide.md b/docs/authentication-guide.md new file mode 100644 index 0000000000..139682805b --- /dev/null +++ b/docs/authentication-guide.md @@ -0,0 +1,116 @@ +# Authentication with Magic and Oauth2 + +1. [Getting started](getting-started.md) +2. [Development and installation](development-guide.md) +3. [Deployment for production](deployment-guide.md) +4. [Authentication and magic tokens](authentication-guide.md) + +--- + +## Contents + +- [Minimum security requirements](#minimum-security-requirements) +- [Authenticated email-based _magic_ login](#authenticated-email-based-magic-login) + - [Magic login workflow](#magic-login-workflow) + - [Oauth2 password login](#oauth2-password-login) + - [Account recovery and reset](#account-recovery-and-reset) +- [Two-factor authentication](#two-factor-authentication) +- [Access and Refresh tokens](#access-and-refresh-tokens) +- [References](#references) + +## Minimum security requirements + +The following is the baseline [recommended approach](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md) for ensuring safe app authentication: + +- Any user account change must require current password verification to ensure that it's the legitimate user. +- Login page and all subsequent authenticated pages must be exclusively accessed over TLS or other strong transport. +- Account recovery must ensure that the starting point is a logged-out account. +- Where a state is unclear, use two tokens (one emailed, one stored in the browser) with a handshaking / fingerprinting protocol to ensure a chain of custody. +- An application should respond with a generic error message regardless of whether: + - The user ID or password was incorrect. + - The account does not exist. + - The account is locked or disabled. +- Code should go through the same process, no matter what, allowing the application to return in approximately the same response time. +- In the words of [George Orwell](https://en.wikipedia.org/wiki/Politics_and_the_English_Language#Remedy_of_Six_Rules), "break any of these rules sooner than do anything outright barbarous". + +[On passwords](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Password_Storage_Cheat_Sheet.md): +- Use `Argon2id` with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism. +- Passwords shorter than 8 characters are considered to be weak (NIST SP800-63B). +- Maximum password length of 64 prevents long password Denial of Service attacks. +- Do not silently truncate passwords. +- Allow usage of all characters, including unicode and whitespace. + +## Authenticated email-based magic login + +Most web applications permit account recovery through requesting a password reset via email. This is a weak point in the custodial chain _even_ assuming a savvy user adhering to best-practice password conventions. In which case, secure this and offer it as a login option ... a _magic_ login. + +> Any custodial changes to user-controlled information must be treated as requiring full authentication. Do **not** assume that a logged-in user is the authorised account holder. + +### Magic login workflow + +- Ensure the user is logged out. +- User enters email in `frontend` and submits via API to `backend`. +- Check if an account exists, if not silently create one, and get the user `id`. +- Generate two 30-second-duration magic JWT tokens, each with a randomly-generated UUID `sub` (subject) and `fingerprint`, where `sub1 != sub2` and `fingerprint1 == fingerprint2`. +- One is emailed to the user (with `sub = user.id`) and the other is returned to the `frontend` to be stored in the user's browser. +- Once the user clicks on (or pastes) the magic / emailed link and returns to the browser, check that the `fingerprint` in the stored and submitted tokens are the same, and submit both to the `backend`. +- Validate the tokens, and check compliance with `sub` and `fingerprint` rules. +- If the custodial chain is secure, return an `access_token` and `refresh_token` to the `frontend`. +- Note that the tokens provide no meaningful information to an adversary. No email address or personal information. + +### Oauth2 password login + +Users may not always have access to email, or use a password manager, making it easier (and faster) to login with a password. A fallback to a stored password must be offered. You could also choose to enforce passwords for admin users. + +### Account recovery and reset + +Clearly a user does not _need_ a password to login with this process, and so there is no enforcement in the core stack. However, an application may require evidence of a deliberate decision in the custodial chain. Enforcing a password is part of that chain, and that raises the need to reset a password if it is ever lost. + +Password recovery functions much the same as the magic workflow, with the same dual token validation process, except that the user now goes to a page that permits them to save a new password. + +The user can also change their password while logged in, but - mindful of the rules for validation - they will need to provide their original password before doing so. + +## Two-factor authentication + +Time-based One-Time Password (TOTP) authentication extends the login process to include a _challenge-response_ component where the user needs to enter a time-based token _after_ their preferred login method. + +This requires that the user: + +- Install an authenticator app. +- Generate a QR code or key and pair that with their app. +- Confirm that they are paired. + +After that, the user will be challenged to enter a 6-digit verification code to conclude each login. + +The login workflow is extended as follows: + +- TOTP requires the use of third-party token generators, and they seem to be stuck on `sha-1` hashing. That means deliberately dumbing down from `sha256`. +- After successful login (oauth2 or magic) instead of generating `access_token` and `refresh_token`, **instead** generate a special `access_token` with `totp = True` as a key in the token. +- Specifically test for this in each authentication check. `totp = True` can **only** be used to verify a TOTP submission, not for any other purpose. The user is not considered to be authenticated. +- When the user submits their verification code, `post` that, plus the `access_token` with `totp = True`, to complete authentication and receive the standard `access_token` and `refresh_token`. + +As before, enabling or disabling TOTP requires full authentication with a password. + +## Access and Refresh tokens + +Persisting the authentication `state` of a user requires a mechanism to respond to an authentication challenge which does not inconvenience the user, while still maintaining security. + +The standard method for doing so is via `access_token` and `refresh_token`, where: + +- The access token is of short duration (30 minutes, or even less). +- The refresh token is of long duration (3 months, and sometimes indefinite). +- An access token can only be used to authenticate the user, and a refresh token can only be used to generate new access tokens. +- Access tokens are not stored, and refresh tokens are maintained in the database ... meaning they must be deliberately deactivated on use. +- When a user logs out, deactivate their refresh tokens. + +Obviously, this still means that a long-living, active `refresh_token` is equivalent to authentication, which returns us to the caveat raised above: + +> Any custodial changes to user-controlled information must be treated as requiring full authentication. Do **not** assume that a logged-in user is the authorised account holder. + +## References + +- [Python JOSE](https://python-jose.readthedocs.io/) to generate JWT tokens. +- [PassLib](https://passlib.readthedocs.io/) to manage hashing and TOTP. +- [OWASP authentication cheat sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md) +- [OWASP password storage cheat sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Password_Storage_Cheat_Sheet.md) +- [Ensuring randomness](https://blog.cloudflare.com/ensuring-randomness-with-linuxs-random-number-generator/) \ No newline at end of file diff --git a/DEPLOYMENT-README.md b/docs/deployment-guide.md similarity index 87% rename from DEPLOYMENT-README.md rename to docs/deployment-guide.md index 1c0c6cbb86..7080905c12 100644 --- a/DEPLOYMENT-README.md +++ b/docs/deployment-guide.md @@ -1,4 +1,25 @@ -# Step-by-step production deployment guide +# Deployment guide for production + +1. [Getting started](getting-started.md) +2. [Development and installation](development-guide.md) +3. [Deployment for production](deployment-guide.md) +4. [Authentication and magic tokens](authentication-guide.md) + +--- + +## Contents + +- [Preparation](#preparation) + - [Committing to GitHub](#committing-to-github) + - [DigitalOcean Droplets](#digital-ocean-droplets) + - [Domain name and email](#domain-name-and-email) +- [Deployment](#deployment) + - [Docker](#docker) + - [Clone your repository](#clone-your-repository) + - [Docker Swarm Mode](#docker-swarm-mode) + - [Traefik Proxy with HTTPS](#traefik-proxy-with-https) + - [Deploy to a Docker Swarm mode cluster](#deploy-to-a-docker-swarm-mode-cluster) +- [Production URLs](#production-urls) There are easier ways to run a blog. You will find the resource requirements for this stack quite substantive. This really is if you intend to run some complex web service and need all the architecture. @@ -288,24 +309,15 @@ You may need to prune regularly while developing if you find yourself running ou docker system prune ``` -## URLs - -These are the URLs that will be used and generated by the project. - -### Production URLs - -Production URLs, from the branch `production`. - -Frontend: https://example.com - -Backend: https://example.com/api/ - -Automatic Interactive Docs (Swagger UI): https://example.com/docs - -Automatic Alternative Docs (ReDoc): https://example.com/redoc - -PGAdmin: https://pgadmin.example.com +## Production URLs -Flower: https://flower.example.com +These are the URLs served in production (replace `example.com` with your own): -Traefik: https://traefik.example.com \ No newline at end of file +- Frontend: https://example.com +- Backend: https://example.com/api/ +- Automatic Interactive Docs (Swagger UI): https://example.com/docs +- Automatic Alternative Docs (ReDoc): https://example.com/redoc +- PGAdmin: https://pgadmin.example.com +- Flower: https://flower.example.com +- Traefik: https://traefik.example.com +- Neo4j: https://neo4j.example.com \ No newline at end of file diff --git a/docs/development-guide.md b/docs/development-guide.md new file mode 100644 index 0000000000..03b0cfdb17 --- /dev/null +++ b/docs/development-guide.md @@ -0,0 +1,130 @@ +# Development and cookiecutter installation + +1. [Getting started](getting-started.md) +2. [Development and installation](development-guide.md) +3. [Deployment for production](deployment-guide.md) +4. [Authentication and magic tokens](authentication-guide.md) + +--- + +## Contents + +- [Run Cookiecutter](#run-cookiecutter) +- [Generate passwords](#generate-passwords) +- [Input variables](#input-variables) +- [Local development](#local-development) +- [Starting Jupyter Lab](#starting-jupyter-lab) + +## Run Cookiecutter + +Go to the directory where you want to create your project and run: + +```bash +pip install cookiecutter +cookiecutter https://github.com/whythawk/full-stack-fastapi-postgresql +``` + +## Generate passwords + +You will be asked to provide passwords and secret keys for several components. Open another terminal and run: + +```bash +openssl rand -hex 32 +# Outputs something like: 99d3b1f01aa639e4a76f4fc281fc834747a543720ba4c8a8648ba755aef9be7f +``` + +Copy the contents and use that as password / secret key. And run that again to generate another secure key. + +## Input variables + +The generator (Cookiecutter) will ask you for data on a long list of fields which will be used to populate variables across the project, customising it for you out the box. You might want to have these on hand before generating the project. + +The input variables, with their default values (some auto generated) are: + +- `project_name`: The name of the project. This will also be the folder in which your project is generated. +- `project_slug`: The development friendly name of the project. By default, based on the project name +- `domain_main`: The domain in where to deploy the project for production (from the branch `production`), used by the load balancer, backend, etc. By default, based on the project slug. +- `domain_staging`: The domain in where to deploy while staging (before production) (from the branch `master`). By default, based on the main domain. +- `domain_base_api_url`: The domain url used by the frontend app for backend api calls. If deploying a localhost development environment, likely to be `http://localhost/api/v1` +- `domain_base_ws_url`: The domain url used by the frontend app for backend websocket calls. If deploying a localhost development environment, likely to be `ws://localhost/api/v1` +- `docker_swarm_stack_name_main`: The name of the stack while deploying to Docker in Swarm mode for production. By default, based on the domain. +- `docker_swarm_stack_name_staging`: The name of the stack while deploying to Docker in Swarm mode for staging. By default, based on the domain. +- `secret_key`: Backend server secret key. Use the method above to generate it. +- `first_superuser`: The first superuser generated, with it you will be able to create more users, etc. By default, based on the domain. +- `first_superuser_password`: First superuser password. Use the method above to generate it. +- `backend_cors_origins`: Origins (domains, more or less) that are enabled for CORS (Cross Origin Resource Sharing). This allows a frontend in one domain (e.g. `https://dashboard.example.com`) to communicate with this backend, that could be living in another domain (e.g. `https://api.example.com`). It can also be used to allow your local frontend (with a custom `hosts` domain mapping, as described in the project's `README.md`) that could be living in `http://dev.example.com:8080` to communicate with the backend at `https://stag.example.com`. Notice the `http` vs `https` and the `dev.` prefix for local development vs the "staging" `stag.` prefix. By default, it includes origins for production, staging and development, with ports commonly used during local development by several popular frontend frameworks (Vue with `:8080`, React, Angular). +- `smtp_port`: Port to use to send emails via SMTP. By default `587`. +- `smtp_host`: Host to use to send emails, it would be given by your email provider, like Mailgun, Sparkpost, etc. +- `smtp_user`: The user to use in the SMTP connection. The value will be given by your email provider. +- `smtp_password`: The password to be used in the SMTP connection. The value will be given by the email provider. +- `smtp_emails_from_email`: The email account to use as the sender in the notification emails, it could be something like `info@your-custom-domain.com`. +- `smtp_emails_from_name`: The email account name to use as the sender in the notification emails, it could be something like `Symona Adaro`. +- `smtp_emails_to_email`: The email account to use as the recipient for `contact us` emails, it could be something like `requests@your-custom-domain.com`. +- `postgres_password`: Postgres database password. Use the method above to generate it. (You could easily modify it to use MySQL, MariaDB, etc). +- `pgadmin_default_user`: PGAdmin default user, to log-in to the PGAdmin interface. +- `pgadmin_default_user_password`: PGAdmin default user password. Generate it with the method above. +- `neo4j_password`: Neo4j database password. Use the method above to generate it. +- `traefik_constraint_tag`: The tag to be used by the internal Traefik load balancer (for example, to divide requests between backend and frontend) for production. Used to separate this stack from any other stack you might have. This should identify each stack in each environment (production, staging, etc). +- `traefik_constraint_tag_staging`: The Traefik tag to be used while on staging. +- `traefik_public_constraint_tag`: The tag that should be used by stack services that should communicate with the public. +- `flower_auth`: Basic HTTP authentication for flower, in the form`user:password`. By default: "`admin:changethis`". +- `sentry_dsn`: Key URL (DSN) of Sentry, for live error reporting. You can use the open source version or a free account. E.g.: `https://1234abcd:5678ef@sentry.example.com/30`. +- `docker_image_prefix`: Prefix to use for Docker image names. If you are using GitLab Docker registry it would be based on your code repository. E.g.: `git.example.com/development-team/my-awesome-project/`. +- `docker_image_backend`: Docker image name for the backend. By default, it will be based on your Docker image prefix, e.g.: `git.example.com/development-team/my-awesome-project/backend`. And depending on your environment, a different tag will be appended ( `prod`, `stag`, `branch` ). So, the final image names used will be like: `git.example.com/development-team/my-awesome-project/backend:prod`. +- `docker_image_celeryworker`: Docker image for the celery worker. By default, based on your Docker image prefix. +- `docker_image_frontend`: Docker image for the frontend. By default, based on your Docker image prefix. + +## Local development + +Once the Cookiecutter script has completed, you will have a folder populated with the base project and all input variables customised. + +Change into the project folder and run the `docker-compose` script to build the project containers: + +```bash +docker-compose build --no-cache +``` + +And start them: + +```bash +docker-compose up -d +``` + +**NOTE:** The Nuxt image does not automatically refresh while running in development mode. Any changes will need a rebuild. This gets tired fast, so it's easier to run Nuxt outside Docker and call through to the `backend` for API calls. You can then view the frontend at `http://localhost:3000` and the backend api endpoints at `http://localhost/redoc`. This problem won't be a concern in production. + +Change into the `/frontend` folder, and: + +```bash +yarn install +yarn dev +``` + +Be careful about the version of `Node.js` you're using. As of today (December 2022), the latest Node version supported by Nuxt is 16.18.1. + +FastAPI `backend` updates will refresh automatically, but the `celeryworker` container must be restarted before changes take effect. + +## Starting Jupyter Lab + +If you like to do algorithmic development and testing in Jupyter Notebooks, then launch the `backend` terminal and start Jupyter as follows: + +```bash +docker-compose exec backend bash +``` + +From the terminal: + +```bash +$JUPYTER +``` + +Copy the link generated into your browser and start. + +**NOTE:** Notebooks developed in the container are not saved outside, so remember to copy them for persistence. You can do that from inside Jupyter (download), or: + +```bash +docker cp :/file/path/within/container /host/path/target +``` + +Or share a folder via `docker-compose.override.yml`. + +At this point, development is over to you. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000000..81692541a8 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,139 @@ +# Getting started with the Base Project Generator + +1. [Getting started](getting-started.md) +2. [Development and installation](development-guide.md) +3. [Deployment for production](deployment-guide.md) +4. [Authentication and magic tokens](authentication-guide.md) + +--- + +## Contents + +- [What is it?](#what-is-it) +- [Who is it for?](#who-is-it-for) +- [What does it look like?](#what-does-it-look-like) +- [How to use it](#how-to-use-it) +- [Release notes](#release-notes) +- [License](#license) + +## What is it? + +This FastAPI, PostgreSQL, Neo4j & Nuxt 3 repo will generate a complete web application stack as a foundation for your project development. + +It consists of the following key components: + +- **Docker Compose** integration and optimization for local development. +- **Authentication** user management schemas, models, crud and apis already built, with OAuth2 JWT token support & default hashing. Offers _magic link_ authentication, with password fallback, with cookie management, including `access` and `refresh` tokens. +- [**FastAPI**](https://github.com/tiangolo/fastapi) backend with [Inboard](https://inboard.bws.bio/) one-repo Docker images: + - **SQLAlchemy** version 2.0 support for models. + - **MJML** templates for common email transactions. + - **Metadata Schema** based on [Dublin Core](https://www.dublincore.org/specifications/dublin-core/dcmi-terms/#section-3) for inheritance. + - **Common CRUD** support via generic inheritance. + - **Standards-based**: Based on (and fully compatible with) the open standards for APIs: [OpenAPI](https://github.com/OAI/OpenAPI-Specification) and [JSON Schema](http://json-schema.org/). + - [**Many other features**]("https://fastapi.tiangolo.com/features/"): including automatic validation, serialization, interactive documentation, etc. +- [**Nuxt/Vue 3**](https://nuxt.com/) frontend: + - **Authorisation** via middleware for page access, including logged in or superuser. + - **Model blog** project, with [Nuxt Content](https://content.nuxtjs.org/) for writing Markdown pages. + - **Form validation** with [Vee-Validate 4](https://vee-validate.logaretm.com/v4/). + - **State management** with [Pinia](https://pinia.vuejs.org/), and persistance with [Pinia PersistedState](https://prazdevs.github.io/pinia-plugin-persistedstate/). + - **CSS and templates** with [TailwindCSS](https://tailwindcss.com/), [HeroIcons](https://heroicons.com/), and [HeadlessUI](https://headlessui.com/). +- **PostgreSQL** database. +- **PGAdmin** for PostgreSQL database management. +- **Celery** worker that can import and use models and code from the rest of the backend selectively. +- **Flower** for Celery jobs monitoring. +- **Neo4j** graph database, including integration into the FastAPI base project. +- Load balancing between frontend and backend with **Traefik**, so you can have both under the same domain, separated by path, but served by different containers. +- Traefik integration, including Let's Encrypt **HTTPS** certificates automatic generation. +- GitLab **CI** (continuous integration), including frontend and backend testing. + +## Who is it for? + +This project is a rock-solid foundation on which to build complex web applications which need parallel processing, scheduled event management, and a range of relational and graph database support. The base deployment - with PostgreSQL and Neo4j - takes up about 10Gb, and requires about 2Gb of memory to run. + +This is **not** a light-weight system to deploy a blog or simple content-management-system. + +It is for developers looking to build and maintain full feature progressive web applications that can run online, or offline, want the complex-but-routine aspects of auth 'n auth, and component and deployment configuration taken care of. + +## What does it look like? + +### App landing page + +![Landing page](../img/landing.png) + +### Dashboard Login + +![Magic-link login](../img/login.png) + +### Dashboard User Management + +![Moderator user management](../img/dashboard.png) + +### Interactive API documentation + +![Interactive API docs](../img/redoc.png) + +### Enabling two-factor security (TOTP) + +![Enabling TOTP](../img/totp.png) + +## How to use it + +### Installing for local development + +Running Cookiecutter to customise the deployment with your settings, and then building with Docker compose, takes about 20 minutes. + +- [Development and installation](development-guide.md) + +### Deploying for production + +This stack can be adjusted and used with several deployment options that are compatible with Docker Compose, but it is designed to be used in a cluster controlled with pure Docker in Swarm Mode with a Traefik main load balancer proxy handling automatic HTTPS certificates, using the ideas from [DockerSwarm.rocks](https://dockerswarm.rocks). + +- [Deployment for production](deployment-guide.md) + +### Authentication with magic and TOTP + +Time-based One-Time Password (TOTP) authentication extends the login process to include a challenge-response component where the user needs to enter a time-based token after their preferred login method. + +- [Authentication and magic tokens](authentication-guide.md) + +### More details + +After using this generator, your new project will contain an extensive `README.md` with instructions for development, deployment, etc. You can pre-read [the project `README.md` template here too](../{{cookiecutter.project_slug}}/README.md). + +## Release Notes + +See notes and [releases](https://github.com/whythawk/full-stack-fastapi-postgresql/releases). The last four release notes are listed here: + +### 0.7.3 +- @nuxt/content 2.2.1 -> 2.4.3 +- Fixed: `@nuxt/content` default api, `/api/_content`, conflicts with the `backend` api url preventing content pages loading. +- Documentation: Complete deployment guide in `DEPLOYMENT-README.md` (this has now been moved to `/docs`) + +### 0.7.2 +- Fixed: URLs for recreating project in generated `README.md`. PR [#15](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/15) by @FranzForstmayr +- Fixed: Absolute path for mount point in `docker-compose.override.yml`. PR [#16](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/16) by @FranzForstmayr +- Fixed: Login artifacts left over from before switch to magic auth. PR [#18](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/18) by @turukawa and @FranzForstmayr +- New: New floating magic login card. PR [#19](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/19) by @turukawa +- New: New site contact page. PR [#20](https://github.com/whythawk/full-stack-fastapi-postgresql/pull/20) by @turukawa + +### 0.7.1 + +- SQLAlchemy 1.4 -> 2.0 +- Nuxt.js 3.0 -> 3.2.2 +- Fixed: `tokenUrl` in `app/api/deps.py`. Thanks to @Choiuijin1125. +- Fixed: SMTP options for TLS must be `ssl`. Thanks to @raouldo. +- Fixed: `libgeos` is a dependency for `shapely` which is a dependency for `neomodel`, and which doesn't appear to be installed correctly on Macs. Thanks to @valsha and @Mocha-L. +- Fixed: `frontend` fails to start in development. Thanks to @pabloapast and @dividor. + +### 0.7.0 + +- New feature: magic (email-based) login, with password fallback +- New feature: Time-based One-Time Password (TOTP) authentication +- Security enhancements to improve consistency, safety and reliability of the authentication process (see full description in the frontend app) +- Requires one new `frontend` dependency: [QRcode.vue](https://github.com/scopewu/qrcode.vue) + +[Historic changes from original](https://github.com/tiangolo/full-stack-fastapi-postgresql#release-notes) + +## License + +This project is licensed under the terms of the MIT license. diff --git a/img/dashboard.png b/img/dashboard.png index defd06d803..092bb07dd0 100644 Binary files a/img/dashboard.png and b/img/dashboard.png differ diff --git a/img/totp.png b/img/totp.png index 0aca22e04b..0a32cc020f 100644 Binary files a/img/totp.png and b/img/totp.png differ