User logout endpoint + token blacklisting

This commit is contained in:
Marcin Armacki 2020-11-15 18:51:06 +01:00
parent e2951b4fa4
commit 253e61b2a1
6 changed files with 137 additions and 28 deletions

View File

@ -12,3 +12,12 @@ class CouldNotParse(Exception):
class KnownError(Exception):
pass
class TokenBlacklisted(Exception):
pass
class UserAuthFailed(Exception):
pass
class NotTokenOwner(Exception):
pass

View File

@ -28,3 +28,6 @@ class Paragraph(models.Model):
label = models.CharField(max_length=255)
user_updated = models.BooleanField(default=False)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Token(models.Model):
token = models.TextField()

View File

@ -13,5 +13,20 @@ DatabaseAddSuccess = {"message": "Data successfully added to database",
DatabaseAddFail = {"message": "Failed adding data to database",
"statusCode": 500}
ParseFailed = {"messeger": "Couldn't parse data",
ParseFailed = {"messege": "Couldn't parse data",
"statusCode": 500}
TokenBlacklistCode = {"message": "Error: Token is blacklisted",
"statusCode": 409}
UserAuthFailedCode = {"message": "Error: User authentication failed",
"statusCode": 401}
InvalidFormCode = {"message": "Error: File is missing",
"statusCode": 422}
UserLogoutSuccessCode = {"message": "User logout successully",
"statusCode": 200}
NotTokenOwnerCode = {"message": "Error: Token ownership check failed",
"statusCode": 401}

View File

@ -1,4 +1,8 @@
import time
import time, jwt
from django.conf import settings
from prototype.filehandler.models import Token
from prototype.filehandler.statusCodes import UnknownError
def verifyExpiration(payload):
if "exp" not in payload or int(time.time()) > int(payload["exp"]):
return False
@ -10,3 +14,39 @@ def verifyContent(payload):
actualKeys = [*payload]
isProperStructure = all([True if key in actualKeys else False for key in expectedKeys])
return all([isProperStructure, payload["iss"] == "NKADF"])
def verifyIfBlacklisted(token):
try:
entry = Token.objects.get(token = token)
if entry:
return True
except Token.MultipleObjectsReturned:
return True
except:
return False
def createToken(user_id):
try:
iat = int(time.time())
exp = iat + 86400
payload = {"iss": "NKADF",
"iat": iat,
"sub": user_id,
"exp": exp}
jwt_token = jwt.encode(payload, settings.SECRET_KEY, algorithm = "HS256").decode("utf-8")
result = {"message": "User authenticated successfully",
"statusCode": 200,
"token": jwt_token,
"exp": exp}
except:
result = UnknownError
finally:
return result
def blacklistToken(token):
try:
token = Token(token = token)
token.save()
return True
except:
return False

View File

@ -3,7 +3,9 @@ import time
from django.contrib.auth import authenticate
from django.conf import settings
from django.contrib.auth.models import User
from prototype.filehandler.tokenAuthModule import verifyExpiration, verifyContent
from prototype.filehandler.tokenAuthModule import verifyExpiration, verifyContent, createToken, blacklistToken, verifyIfBlacklisted
from prototype.filehandler.exceptions import TokenBlacklisted, UserAuthFailed, NotTokenOwner
from prototype.filehandler.statusCodes import TokenBlacklistCode, UserAuthFailedCode, UserLogoutSuccessCode, NotTokenOwnerCode, UnknownError
def registerNewUser(login, password):
try:
@ -23,22 +25,11 @@ def loginUser(login, password):
try:
user = authenticate(username = login, password = password)
if isinstance(user, User):
iat = int(time.time())
exp = iat + 86400
payload = {"iss": "NKADF",
"iat": iat,
"sub": user.id,
"exp": exp}
jwt_token = jwt.encode(payload, settings.SECRET_KEY, algorithm = "HS256").decode("utf-8")
result = {"message": "User authenticated successfully",
"statusCode": 200,
"token": jwt_token,
"exp": exp}
result = createToken(user.id)
else:
raise UserAuthFailed("Error: User authentication failed")
raise UserAuthFailed
except UserAuthFailed as error:
result = {"message": str(error),
"statusCode": 401}
result = UserAuthFailedCode
finally:
return result
@ -46,6 +37,8 @@ def checkUserToken(request):
try:
if "HTTP_BEARER" not in request.META:
raise Exception("Error: Token is missing", 401)
if verifyIfBlacklisted(request.META["HTTP_BEARER"]):
raise TokenBlacklisted
payload = jwt.decode(request.META["HTTP_BEARER"], settings.SECRET_KEY, algorithm = "HS256")
if not verifyExpiration(payload):
raise Exception("Error: Token has expired", 401)
@ -58,9 +51,30 @@ def checkUserToken(request):
result = {"messege": "Error: Invalid token given",
"statusCode": 400}
user_id = None
except Exception as error:
result = {"message": str(error.args[0]),
"statusCode": int(error.args[1])}
except TokenBlacklisted:
result = TokenBlacklistCode
user_id = None
except:
result = UnknownError
user_id = None
finally:
return result, user_id
def logoutUser(login, user_id, token):
try:
user = User.objects.get(pk = user_id)
if not user.username == login:
raise NotTokenOwner
if blacklistToken(token):
result = UserLogoutSuccessCode
else:
raise Exception
except User.DoesNotExist:
result = {"message": "Error: Invalid token given",
"statusCode": 400}
except NotTokenOwner:
result = NotTokenOwnerCode
except:
result = UnknownError
finally:
return result

View File

@ -13,11 +13,11 @@ from prototype.filehandler.forms import DocumentForm
from prototype.filehandler.xmlParser import parseData
from prototype.filehandler.functions import addToDatabase, listDiscussionsFromFile, listParagraphsFromDiscussion, createLabels, listPostsFromDiscussion, updateLabelsByParagraphId
from prototype.filehandler.userModule import registerNewUser, loginUser, checkUserToken
from prototype.filehandler.userModule import registerNewUser, loginUser, logoutUser, checkUserToken
from prototype.filehandler.fileModule import addDataToDatabase
from prototype.filehandler.exceptions import InvalidForm, CouldNotParse, InvalidMethod, KnownError
from prototype.filehandler.statusCodes import MethodNotAllowed, FileUploadSuccess, UnknownError, ParseFailed
from prototype.filehandler.statusCodes import MethodNotAllowed, FileUploadSuccess, UnknownError, ParseFailed, InvalidFormCode
def home(request):
@ -92,12 +92,37 @@ def user(request):
@csrf_exempt
def login(request):
if request.method == 'POST':
data = json.loads(request.body.decode("utf-8"))
result = loginUser(data["email"], data["password"])
try:
if request.method == 'POST':
data = json.loads(request.body.decode("utf-8"))
result = loginUser(data["email"], data["password"])
if result["statusCode"] != 200:
raise KnownError
elif request.method == 'DELETE':
data = json.loads(request.body.decode("utf-8"))
result, user_id = checkUserToken(request)
if result["statusCode"] != 200:
raise KnownError
result = logoutUser(data["email"], user_id, request.META["HTTP_BEARER"])
if result["statusCode"] != 200:
raise KnownError
else:
raise InvalidMethod
except InvalidMethod:
result = MethodNotAllowed
except KnownError:
pass
except:
result = UnknownError
finally:
return JsonResponse(result, status = result["statusCode"])
else:
return JsonResponse(MethodNotAllowed, status = MethodNotAllowed["statusCode"])
@csrf_exempt
def testToken(request):
@ -135,8 +160,11 @@ def file(request):
data = list(Document.objects.filter(user_id = user_id).values("id", "file", "uploaded_at"))
noStatus = True
else:
raise InvalidMethod
except InvalidForm:
result = UnknownError
result = InvalidFormCode
except InvalidMethod:
result = MethodNotAllowed