Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication with Swagger #27

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,4 @@ dmypy.json

# Cython debug symbols
cython_debug/
.DS_Store
140 changes: 139 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,142 @@ To make sure Docker automatically starts on boot, run

`$ sudo systemctl enable docker`

this shouldn't be necessary, but you may as well run it just to be safe.
this shouldn't be necessary, but you may as well run it just to be safe.



## JWT Tokens And Authentication

> JWT is the JSON Web Token authentication plugin for the Django REST Framework.

> JWT generates, access and refresh tokens for Users wanting to access information from the API

>The command `permission_classes = (permissions.IsAuthenticated,)` located in `views.py` ensure that in order to access information of the API you must have logged in with your access token.

* In order to get a JWT Token, you have to register a new user.

* When a new user is created, email verification is sent to their email. You can try this by creating a user for yourself

* After the User has been registered and has verified their email, they are able to login to the API (View Procedure to Login to the API Below)

* Upon Logging In, when correct credentials are entered, you receive a 201 response from the API with the entered username, email and two tokens generated.


After you receive the tokens by logging in. You will notice that there is one that states, "Refresh" and another one that states "Access".

* The Refresh token has a life span of 30 days (which can be changed in `settings.py` ) and is used to generate a new token when required.

* The Acces token enables the users to have direct access to the API, it has a life span of 20 minutes (which can be changed in `settings.py` )

* When you have these tokens you can then login into the API interface and access all features.

## How To Login A User

> To be able to use the API and access all the information, a user has to login with generated access token

To use the login feature and fully have access to the API, run these command in terminal:

$ export DB_PASSWORD=arbitrarypassword
$ docker-compose run web python3 manage.py migrate
$ docker-compose up

On your web browser access the API using http://localhost:8000


* When the API has loaded up, you would see an interphace with various function as shown in the image below

![Swagger Interface](/docs/images/SW-1.PNG?raw=true)

* Click the button that states `(Authorize)`

* Upon clicking Authorize you will see a pop-up requesting for API Key

![Swagger Interface: Authorise Function](/docs/images/SW-2.PNG?raw=true)

* At this point enter Bearer followed by the ACCESS token and click Authorize `Example:(Bearer **************)`. This will give you full access to the API and its functions

## How To Use Login Feature For User And Get Tokens

>To be able to use the API and access all the information, a user has to login to access the login tokens

To use the login feature for a user follow the following steps, run these command in terminal:

$ export DB_PASSWORD=arbitrarypassword
$ docker-compose run web python3 manage.py migrate
$ docker-compose up

On your web browser access the API using http://localhost:8000

* When the API has loaded up, you would see an interphace with various function as shown in the image below

![Swagger Interface](/docs/images/SW-3.PNG?raw=true)

* This shows various options and function. Click the function that states `(“/auth/login/)`

* Upon click `(“/auth/login/)`, you will see an option that states `Try it out`, this will show you an area where you will have to input some parameters (email and password) accordingly, to test use sample users listed below.

* Once this has been entered click `EXECUTE`

* Please note, if you have registered a user and not verifid the email sent, you will be unable to login.

![Swagger Interface: Login Feature](/docs/images/SW-4.PNG?raw=true)

* If the current credentials have been entered you will see a Response Code 200 (OK) with the username and email entered, and the two tokens (REFRESH AND ACCESS TOKENS) showing that the user has been successfully registered.

![Swagger Interface: User Login Features With Tokens ](/docs/images/SW-5.PNG?raw=true)

## How To Register A User

> To be able to use the API and access all the information, a user has to be created to access the login tokens

To create a user follow the following steps, run these command in terminal:

$ export DB_PASSWORD=arbitrarypassword
$ docker-compose run web python3 manage.py migrate
$ docker-compose up

On your web browser access the API using http://localhost:8000


* When the API has loaded up, you would see an interphace with various function as shown in the image below

![Swagger Interface](/docs/images/SW-6.PNG?raw=true)

![Swagger Interface](/docs/images/SW-7.PNG?raw=true)

* This shows various options and function. Click the function that states `(“/auth/register/)`

* Upon click `(“/auth/register/)`, you will see an option that states try it out, this will show you an area where you will have to input some parameters (email, username and password) accordingly.

* Once this has been entered click `EXECUTE`

![Swagger Interface: Register Function](/docs/images/SW-8.PNG?raw=true)

* After a few seconds you will see a Response Code 201 (CREATED) with the username and email entered, showing that the user has been successfully registered.

![Swagger Interface: User Successfully Registered](/docs/images/SW-9.PNG?raw=true)


## How To Create Superuser

>In order to log into the admin interface, you have to create a superuser.

To create a super user follow the following steps, run these command in terminal:

$ export DB_PASSWORD=arbitrarypassword
$ docker-compose run web python3 manage.py createsuperuser
$ Fill in the details accordingly

## How To Log In To Admin Interface

> In order to access the admin interface , you have to use your created superuser details.

In order to acces the admin interphace follow the following steps, run these command in terminal:

$ export DB_PASSWORD=arbitrarypassword
$ docker-compose run web python3 manage.py migrate
$ docker-compose up

Access localhost http://localhost:8000/admin from your web browser

Login in with superuser details
Empty file.
Empty file.
24 changes: 24 additions & 0 deletions django/authentication/Tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from rest_framework.test import APITestCase
from django.urls import reverse

#Reverse takes in a view name and gives us a path to the route
class TestSetUp(APITestCase):

def setUp(self):
self.register_url = reverse('register')
self.register_url = reverse('login')

self.user_data= {
'email':"new_test@gmail.com",
'username':"testemail",
'password':"Password1",
}

return super().setUp()

def tearDown(self):
return super().tearDown()




20 changes: 20 additions & 0 deletions django/authentication/Tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from .test_setup import TestSetUp

class TestViews(TestSetUp):
def test_user_cannot_register_without_data(self):
res = self.client.post(self.register_url)
self.assertEqual(res.status_code , 400)

def test_user_can_register_correctly(self):
res = self.client.post(
self.register_url, self.user_data, format="json")
#self.assertEqual(res.data['email'] ,self.user_data['email'])
Copy link
Member

Choose a reason for hiding this comment

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

Were these temporarily commented out because they were failing? If so, I would suggest fixing the issue causing them to fail and uncommenting.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I was having an import Issue. Sorting it out now

#self.assertEqual(res.data['username'] ,self.user_data['username'])
self.assertEqual(res.status_code , 201)

def test_user_cannot_login_with_unverified_email(self):
self.client.post(
self.register_url, self.user_data, format="json")
res = self.client.post(self.login_url, self.user_data, format="json")
self.assertEqual(res.status_code, 401)

Empty file.
6 changes: 6 additions & 0 deletions django/authentication/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

# Register your models here.
from .models import User

admin.site.register(User)
5 changes: 5 additions & 0 deletions django/authentication/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AuthenticationConfig(AppConfig):
name = 'authentication'
40 changes: 40 additions & 0 deletions django/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.0.8 on 2020-08-01 19:32

from django.db import migrations, models
import django.db.models.manager


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0011_update_proxy_permissions'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, max_length=90, unique=True)),
('email', models.EmailField(db_index=True, max_length=70, unique=True)),
('is_verified', models.BooleanField(default=False)),
('is_active', models.BooleanField(default=True)),
('is_staff', models.BooleanField(default=False)),
('created_date', models.DateTimeField(auto_now=True)),
('updated_date', models.DateTimeField(auto_now_add=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'abstract': False,
},
managers=[
('object', django.db.models.manager.Manager()),
],
),
]
18 changes: 18 additions & 0 deletions django/authentication/migrations/0002_auto_20200801_2142.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.0.8 on 2020-08-01 21:42

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('authentication', '0001_initial'),
]

operations = [
migrations.AlterModelManagers(
name='user',
managers=[
],
),
]
33 changes: 33 additions & 0 deletions django/authentication/migrations/0003_auto_20200802_0944.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.0.8 on 2020-08-02 09:44

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authentication', '0002_auto_20200801_2142'),
]

operations = [
migrations.RenameField(
model_name='user',
old_name='updated_date',
new_name='created_at',
),
migrations.RenameField(
model_name='user',
old_name='created_date',
new_name='updated_at',
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(db_index=True, max_length=255, unique=True),
),
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(db_index=True, max_length=255, unique=True),
),
]
Empty file.
57 changes: 57 additions & 0 deletions django/authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from django.db import models

# Create your models here.
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin)

from django.db import models
from rest_framework_simplejwt.tokens import RefreshToken


class UserManager(BaseUserManager):

def create_user(self, username, email, password=None):
if username is None:
raise TypeError('Users should have a username')
if email is None:
raise TypeError('Users should have a Email')

user = self.model(username=username, email=self.normalize_email(email))
user.set_password(password)
user.save()
return user

def create_superuser(self, username, email, password=None):
if password is None:
raise TypeError('Password should not be none')

user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user


class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=255, unique=True, db_index=True)
email = models.EmailField(max_length=255, unique=True, db_index=True)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']

objects = UserManager()

def __str__(self):
return self.email

def tokens(self):
refresh = RefreshToken.for_user(self)
return {
'refresh': str(refresh),
'access': str(refresh.access_token)
}
13 changes: 13 additions & 0 deletions django/authentication/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework import renderers
import json
class UserRenderer(renderers.JSONRenderer): #This pre-fixes responses with keywords, this ensures consitent responses in the API
charset = 'utf-8'

def render(self, data, accepted_media_type=None, renderer_context=None):
response = ''

if 'ErrorDetail' in str(data):
response= json.dumps({'errors':data})
else:
response= json.dumps({'data':data})
return response
Loading