Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Add JWT auth #3

Merged
merged 30 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7b1cf20
Add jwt auth
tevariou Nov 27, 2020
55fcb76
Refactor env vars mgmt
tevariou Nov 27, 2020
6c8a9d2
fix: pg env for api
tevariou Nov 27, 2020
8f26924
Add doc
tevariou Nov 27, 2020
140181a
Run api as non root
tevariou Nov 28, 2020
e43cbe9
fix: yarn
tevariou Nov 28, 2020
bfe27e3
restart api on failure
tevariou Nov 28, 2020
142a92a
fix: app and api dockerfiles
tevariou Dec 4, 2020
b5f61ab
feat: split docker-compose
tevariou Dec 5, 2020
a1d3d69
fix: add comment in override if .env.local does not exist
tevariou Dec 5, 2020
20c52aa
App changes to login with API
BPierrick Dec 7, 2020
7c72b73
fix: cors issues
tevariou Dec 7, 2020
79a31a0
[App] Auth / Rapid fix
BPierrick Dec 7, 2020
03fc181
Login form validation rules fix
BPierrick Dec 7, 2020
ceed5c0
feat: bump django version
tevariou Dec 7, 2020
fb8808f
Merge branch 'tr/feat/token_auth' of github.com:arkhn/AVC-Forms into …
tevariou Dec 7, 2020
b0d830c
fix: ignore override
tevariou Dec 7, 2020
20dbc4f
fix: env in dockerfile
tevariou Dec 8, 2020
e7c8337
fix: busybox version
tevariou Dec 9, 2020
5ace132
Added disable rules over some inputs
BPierrick Dec 9, 2020
3841953
fix: code field in model, set uuid by default
tevariou Dec 9, 2020
cb4b147
Merge branch 'tr/feat/token_auth' of github.com:arkhn/AVC-Forms into …
tevariou Dec 9, 2020
3b648fc
[APP] Wired authentication & Patients basic requests with API
BPierrick Dec 9, 2020
dc44112
refactor: deployment
tevariou Dec 9, 2020
d2b68aa
merge
tevariou Dec 9, 2020
76cba8f
fix: cleaning
tevariou Dec 9, 2020
f4c4cb8
fix: useless attributes
tevariou Dec 11, 2020
ba0d080
[APP] Wire front to API
BPierrick Dec 14, 2020
d5e8a6c
fix: env mgmt
tevariou Dec 14, 2020
7fcc096
fix: bump version
tevariou Dec 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .env

This file was deleted.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
**/__pycache__
**.DS_Store
**.iml
**.local
docker-compose.override.yml
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# AVC Forms

## Usage
Run `docker-compose up -d` and visit `host:port` (default `localhost:8080`)
## Local development usage
Use a `docker-compose.override.yml` at the root of the project to override the base configuration.
Run `docker-compose up`. Only the api and the database will run.
Visit `host:port/api` (default `localhost:8080/api`) to access the API interface.
Default user Admin credentials are `{ username: admin, password: admin }`.

## Configuration
Set up your custom configuration in the `.env` file
## Production or demo usage
Run `docker-compose -f docker-compose.yml -f docker-compose.prod.yml up`.
Visit `host:port` (default `localhost:8080`).
7 changes: 7 additions & 0 deletions api/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DJANGO_SUPERUSER_PASSWORD=${DJANGO_SUPERUSER_PASSWORD:-admin}
DJANGO_SUPERUSER_USERNAME=${DJANGO_SUPERUSER_USERNAME:-admin}
DJANGO_SUPERUSER_EMAIL=${DJANGO_SUPERUSER_EMAIL:[email protected]}
DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY:-secret}
DJANGO_DEBUG=${DJANGO_DEBUG:-0}
DJANGO_ALLOWED_HOSTS="${DJANGO_ALLOWED_HOSTS:-localhost 127.0.0.1 [::1]}"
DJANGO_CORS_ALLOWED_ORIGINS="${DJANGO_CORS_ALLOWED_ORIGINS:-http://localhost:8080 http://localhost:3000}"
15 changes: 9 additions & 6 deletions api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ RUN apt-get update && apt-get install -y libpq-dev gcc
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /tmp/wheels -r requirements.txt

FROM python:3.9.0-slim-buster
ENV DJANGO_SETTINGS_MODULE=avc_forms.settings
ENV PYTHONPATH=/api
RUN apt-get update && \
apt-get install -y --no-install-recommends netcat libpq-dev && \
apt-get autoremove -y && \
apt-get clean
COPY --from=builder /tmp/wheels /wheels
RUN pip install --no-cache /wheels/*
WORKDIR /api
COPY . .
RUN groupadd -r api && useradd --create-home --no-log-init -r -g api api
USER api:api
WORKDIR /home/api
ENV PYTHONPATH=/home/api
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already the case if the cwd is /home/api

Copy link
Contributor Author

@tevariou tevariou Dec 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that it's not. uwsgi crash returning a ModuleNotFoundError: No module named 'avc_forms' if I don't specify the python path. PYTHONPATH is undefined in the container if I don't define it manually here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you problem. You can set module=avc_forms.wsgi in the uwsgi.ini, which a pretty standard way.

Note: regarding the uwsgi.ini file, there's a lot of stuff in there. Did you find that everything was required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module and the mount attributes seem to be mutually exclusive. The others are either required (chdir, mount, manage-script-name, socket, chmod-socket, uid, guid) or standard server configuration. I did a bit of cleaning.

ENV PATH /home/api/.local/bin:${PATH}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning supression such as Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. during python package install and it doesn't find uwsgi when it's time to run the server.

ENV DJANGO_SETTINGS_MODULE=avc_forms.settings
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting this here is not required (use a default in the uwsgi.py).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

django-admin returns No Django settings specified if I don't specify this (in docker-entrypoint.sh I'm using django-admin to create a superuser). It seems the default is correctly set in uwsgi.py though.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use python manage.py createsuperuser to create a superuser.

As a rule of thumb, use the manage.py over django-admin when the codebase is available.

COPY --from=builder --chown=api:api /tmp/wheels wheels
RUN pip install --user --no-cache wheels/* && rm -rf wheels
COPY --chown=api:api . .
ENTRYPOINT ["sh", "docker-entrypoint.sh"]
44 changes: 36 additions & 8 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,41 @@
* Admin interface at `host:port/api/admin`

## Routes
* `/api-auth/login` accepts basic authentication
* `/api-auth/logout`
Visit `host:port/api` and login for full API documentation

* `/token/`
* `POST` request
```json
{
"username": "username",
"password": "password"
}
```
* Response
```json
{
"refresh": "refresh_jwt_token",
"access": "access_jwt_token"
}
```
* `/token/refresh/`
* Request
```shell script
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"refresh":"refresh_token_jwt"}' \
http://host:port/api/token/refresh/
```
* Response
```json
{ "access": "access_jwt_token" }
```
* `/users`
* `/patients`
```json
{
"code": "unique_text_field",
"data": { }
}
```
* Example request
```shell script
curl \
-H "Bearer access_jwt_token"
http://host:port/api/patients
```
16 changes: 0 additions & 16 deletions api/avc_forms/asgi.py

This file was deleted.

17 changes: 11 additions & 6 deletions api/avc_forms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'secret')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.getenv('DJANGO_DEBUG', 0))
DEBUG = bool(int(os.getenv('DJANGO_DEBUG', 0)))

ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")

CORS_ALLOW_ALL_ORIGINS = DEBUG
CORS_ALLOWED_ORIGINS = os.environ.get("DJANGO_CORS_ALLOWED_ORIGINS").split(" ")

# Application definition

INSTALLED_APPS = [
'avc_forms.api',
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
Expand All @@ -45,6 +47,7 @@
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
Expand Down Expand Up @@ -123,12 +126,14 @@

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_ROOT = os.path.join(PROJECT_DIR, 'django_static')
STATIC_ROOT = '/tmp/django_static'
STATIC_URL = '/django_static/'

REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 42
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
vmttn marked this conversation as resolved.
Show resolved Hide resolved
'rest_framework.authentication.SessionAuthentication'
),
'PAGE_SIZE': 100
}
6 changes: 6 additions & 0 deletions api/avc_forms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
from django.contrib import admin
from rest_framework import routers
from avc_forms.api import views
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
Expand All @@ -28,5 +32,7 @@
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('admin/', admin.site.urls)
]
6 changes: 4 additions & 2 deletions api/avc_forms/uwsgi.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[uwsgi]
chdir=/api/avc_forms
chdir=/home/api/avc_forms
mount = /api=avc_forms.wsgi:application
manage-script-name=true
master=True
Expand All @@ -9,5 +9,7 @@ max-requests=5000
env=LANG=en_US.UTF-8
processes=5
threads=2
socket=avc_forms.sock
socket=/tmp/avc_forms.sock
chmod-socket=666
uid=api
gid=api
8 changes: 4 additions & 4 deletions api/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#!/bin/bash

# Collect static files
echo "Collect static files"
python manage.py collectstatic --noinput

# Wait for db
echo "Waiting for postgres..."
while ! nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do
Expand All @@ -14,8 +18,4 @@ python manage.py migrate
echo "Create superuser"
django-admin createsuperuser --noinput
vmttn marked this conversation as resolved.
Show resolved Hide resolved

# Collect static files
echo "Collect static files"
python manage.py collectstatic --noinput

exec "$@"
6 changes: 4 additions & 2 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
asgiref==3.3.1
Django==3.1.3
Django==3.1.4
django-cors-headers==3.5.0
django-filter==2.4.0
djangorestframework==3.12.2
djangorestframework-simplejwt==4.6.0
Markdown==3.3.3
psycopg2==2.8.6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psycopg2 is always builded, hence it requires OS packages (at least for bionic). This could be documented in a CONTRIBUTING.md.

PyJWT==1.7.1
pytz==2020.4
sqlparse==0.4.1
uWSGI==2.0.19.1
2 changes: 2 additions & 0 deletions app/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
./node_modules
./build
2 changes: 2 additions & 0 deletions app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
REACT_APP_API_URL=localhost:8080/api
REACT_APP_AUTH_API_URL=http://localhost:8080/api/token/
1 change: 1 addition & 0 deletions app/.yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
network-timeout 600000
22 changes: 10 additions & 12 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
#FROM node:15.3.0-alpine3.10 as builder
#WORKDIR /app
#ENV PATH /app/node_modules/.bin:$PATH
#COPY ./package.json .
#RUN yarn
#COPY . .
#RUN yarn build
#
#FROM busybox
#WORKDIR /app
#COPY --from=builder /app/build .
FROM node:15.3.0 as builder
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY ./package.json .
COPY ./.yarnrc .
COPY ./yarn.lock .
RUN yarn
COPY . .
RUN yarn build

FROM busybox
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why did you use busybox ? (btw you should set a version)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use scratch since I only want to put the build directory in the app-data volume but It crash if I don't specify a CMD to run something... so I use busybox here since it's the smallest image I could find and it shutdowns gracefully.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see.

In terms of engineering, I think it would be best to try to reuse the same image at the company (namely python:3.x-slim), to reduce the cognitive load (same files, same tricks).

But it is your call.

WORKDIR /app
COPY ./build .
COPY --from=builder /app/build build
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@arkhn/ui": "^1.7.4",
"@arkhn/ui": "^1.9.2",
"@date-io/date-fns": "1.x",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
Expand Down
11 changes: 11 additions & 0 deletions app/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const ID_TOKEN_STORAGE_KEY = "ARKHN_ID_TOKEN";
export const TOKEN_DATA_STORAGE_KEY = "ARKHN_TOKEN_DATA";
export const STATE_STORAGE_KEY = "ARKHN_AUTH_STATE";

export const {
REACT_APP_API_URL: API_URL,
REACT_APP_AUTH_API_URL: AUTH_API_URL,
} = process.env;

export const ACCES_TOKEN = "access";
export const REFRESH_TOKEN = "refresh";
26 changes: 15 additions & 11 deletions app/src/navigation/AppNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { BrowserRouter, Route, Link } from "react-router-dom";
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";

import { NavBar } from "@arkhn/ui";
import { makeStyles, Theme, createStyles, Typography } from "@material-ui/core";
Expand All @@ -8,6 +8,8 @@ import LanguageSelect from "../components/LanguageSelect";
import { ReactComponent as Logo } from "../assets/img/arkhn-logo.svg";
import AVCTableViewer from "../screens/AVCTableViewer";
import PatientForm from "../screens/PatientForm";
import PrivateRoute from "./Private";
import Login from "screens/Login";

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand Down Expand Up @@ -40,7 +42,7 @@ const AppNavigator: React.FC<{}> = () => {
title={
<>
<div className={classes.titleContainer}>
<Link className={classes.link} to={"/"}>
<Link className={classes.link} to={"/avc_viewer"}>
<Logo className={classes.logo} />
<Typography variant="h6" color="primary">
AVC Forms
Expand All @@ -52,15 +54,17 @@ const AppNavigator: React.FC<{}> = () => {
}
/>
<div className={classes.body}>
<Route exact path="/">
<AVCTableViewer />
</Route>
<Route exact path="/avc_viewer">
<AVCTableViewer />
</Route>
<Route exact path="/patient_form">
<PatientForm />
</Route>
<Switch>
<Route exact path="/">
<Login />
</Route>
<PrivateRoute path="/avc_viewer">
<AVCTableViewer />
</PrivateRoute>
<PrivateRoute path="/patient_form">
<PatientForm />
</PrivateRoute>
</Switch>
</div>
</BrowserRouter>
</>
Expand Down
Loading