Skip to content

Commit

Permalink
Um user update infos (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-levra authored Feb 2, 2024
1 parent 6d3e670 commit 89d5a52
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 162 deletions.
2 changes: 2 additions & 0 deletions doc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ this documentation details the different endpoints of each microservice.
> ### [/user/oauth/callback/{oauth-service}](../user_management/doc/User_management.md#oauthcallbackauth-service)
> ### [/user/update-infos/](../user_management/doc/User_management.md#userupdate-infos)
## User Stats
> ### [/statistics/user/{id}](../user_stats/doc/user-stats-documentation.md#statisticsuserid)
Expand Down
135 changes: 38 additions & 97 deletions user_management/doc/User_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,6 @@ all fields are mandatory
> | `401` | `application/json` | `{"errors": ["AAA", "BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>errors can be combined
> errors can be :
> - Username empty
> - Username already taken
> - Username length {len(username)} > 20
> - Username must be alphanumeric
> - Email {email} already taken
> - Email empty
> - Email length {len(email)} > 50
> - Email missing @
> - Email missing "." character
> - Email contains more than one @ character
> - Password empty
> - Password length {len(password)} < 8
> - Password missing uppercase character
> - Password missing digit
> - Password missing special character
> - Invalid JSON format in the request body
> - An unexpected error occurred
</details>
Expand Down Expand Up @@ -89,16 +66,6 @@ all fields are mandatory
> | `201` | `application/json` | `{"refresh_token": "eyJhbGci.."}` |
> | `401` | `application/json` | `{"errors": [ "AAA","BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>
> errors can be combined
> errors can be :
> - Username empty
> - Password empty
> - Username not found
> - Invalid password
> - Invalid JSON format in the request body
> - An unexpected error occurred
</details>
Expand Down Expand Up @@ -131,15 +98,6 @@ will return a boolean
> | `401` | `application/json` | `{"errors": [ "AAA","BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>
> errors can be combined
> errors can be :
> - Invalid JSON format in the request body
> - An unexpected error occurred
>
> NB : An empty username is considered as not taken
</details>
## `/user/email-exist/`
Expand Down Expand Up @@ -170,14 +128,6 @@ will return a boolean
> | `401` | `application/json` | `{"errors": [ "AAA","BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>
> errors can be combined
> errors can be :
> - Empty email
> - Invalid JSON format in the request body
> - An unexpected error occurred
</details>
Expand Down Expand Up @@ -208,17 +158,6 @@ all fields are mandatory
> | `400` | `application/json` | `{"errors": ["AAA", "BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>errors can be combined
> errors can be :
> - Refresh token not found
> - Signature verification failed
> - No expiration date found
> - Signature has expired
> - No user_id in payload
> - User does not exist
> - Invalid JSON format in the request body
> - An unexpected error occurred
</details>
Expand Down Expand Up @@ -250,13 +189,6 @@ all fields are mandatory
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
> errors can be :
> - No email provided
> - Email can not be empty
> - Username not found
> - Invalid JSON format in the request body : decode error
> - An unexpected error occurred
</details>
## `/user/forgot-password/check-code/`
Expand Down Expand Up @@ -287,16 +219,6 @@ all fields are mandatory
> | `400` | `application/json` | `{"errors": "AAA", errors details : "aaa" }` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
> errors details are optional
> errors can be :
> - Mandatory value missing : 'email'
> - Mandatory value missing : 'code'
> - Email empty
> - Code empty
> - Username not found
> - Invalid code
> - Code expired
</details>
Expand Down Expand Up @@ -351,19 +273,6 @@ Might be extended to return more information in the future, if needed
> | `400` | `application/json` | `{"errors": "AAA", errors details : "aaa" }` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
> errors details are optional
> errors can be :
> - Mandatory value missing : 'username'
> - Mandatory value missing : 'code'
> - Mandatory value missing : 'password'
> - Email empty
> - Code empty
> - Username not found
> - Invalid code
> - Code expired
> - (all the errors from the is_valid_password function in the sign_up route)
</details>
Expand Down Expand Up @@ -397,11 +306,6 @@ will return a list of usernames that contains the searched username
> | `400` | `application/json` | `{"errors": ["AAA"]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
>
> errors can be :
> - Invalid JSON format in the request body
> - An unexpected error occurred
> - No username found
</details>
Expand Down Expand Up @@ -459,4 +363,41 @@ This endpoint handles the callback after successful OAuth authentication and ret
> | `400` | `application/json` | `{"errors": ["An unexpected error occurred : ..."]}` |
> | `500` | `application/json` | `{"errors": ['Failed to create or get user']}` |
</details>
</details>
## '/user/update-infos/'
### Update user's information
will return 200 if successful
<details>
<summary><code>POST</code><code><b>/user/update-infos/</b></code></summary>
### Parameters
#### Body
mandatory field : change_list, access_token
all other fields are optional and depend on the change_list
> ``` javascript
> {
> "access_token": "d2d040fj..."
> "change_list": ["username", "email", "password"]
> "username": "NewUsername",
> "email": "[email protected]",
> "password": "NewPassword42*"
> }
> NB : change_list must contain at least one of the following values : "username", "email", "password"
> ```
#### Responses
> | http code | content-type | response |
> |-----------|--------------------|------------------------------------------------------|
> | `200` | `application/json` | `{"ok": "ok"}` |
> | `400` | `application/json` | `{"errors": ["AAA", "BBB", "..."]}` |
> | `500` | `application/json` | `{"errors": ['An unexpected error occurred : ...']}` |
56 changes: 56 additions & 0 deletions user_management/src/user/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,59 @@ def test_search_username(self):
self.assertEqual(result.status_code, 200)
self.assertTrue('users' in result.json())
self.assertEqual(len(result.json()['users']), 0)


class TestsUserUpdateInfos(TestCase):
""" 1) first, create a user with /user/signup
2) get the access token of the user just created with /user/refresh-access-jwt
3) then, update the user infos with /user/update-infos
4) finally, check if the user infos have been updated with /user/user-id
5) test invalid data"""
def test_user_update_infos(self):
# 1)
data_preparation = {
'username': 'UpdateThisUser',
'email': '[email protected]',
'password': 'Validpass42*',
}
url = reverse('signup')
result = self.client.post(url, json.dumps(data_preparation), content_type='application/json')
refresh_token = result.json()['refresh_token']
user = User.objects.filter(username='UpdateThisUser').first()

# 2)
url = reverse('refresh-access-jwt')
result = self.client.post(url, json.dumps({'refresh_token': refresh_token}), content_type='application/json')
access_token = result.json()['access_token']

# 3)
data = {
'access_token': access_token,
'change_list': ['username', 'email', 'password'],
'username': 'UpdatedUser',
'email': '[email protected]',
'password': 'AnotherValidpass42*'
}
url = reverse('update-infos')
result = self.client.post(url, json.dumps(data), content_type='application/json')

# 4)
self.assertEqual(result.status_code, 200)
user = User.objects.filter(username='UpdatedUser').first()
self.assertEqual(user.username, 'UpdatedUser')
self.assertEqual(user.email, '[email protected]')

# 5)
data = {
'access_token': access_token,
'change_list': ['username', 'email', 'password'],
'username': 'I',
'email': 'a.fr',
'password': 'aninvalidpassword'
}

url = reverse('update-infos')
result = self.client.post(url, json.dumps(data), content_type='application/json')
self.assertEqual(result.status_code, 400)
self.assertTrue('errors' in result.json())
self.assertTrue(result.json()['errors'])
2 changes: 2 additions & 0 deletions user_management/src/user/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from user.views.search_username import SearchUsernameView
from user.views.sign_in import SignInView
from user.views.sign_up import SignUpView
from user.views.update_infos import UpdateInfos
from user.views.user_id import UserIdView
from user_management import settings

Expand All @@ -27,6 +28,7 @@
path('search-username/', SearchUsernameView.as_view(), name='search-username'),
path('oauth/<str:auth_service>/', OAuth.as_view(), name='oauth'),
path('oauth/callback/<str:auth_service>/', OAuthCallback.as_view(), name='oauth-callback'),
path('update-infos/', UpdateInfos.as_view(), name='update-infos'),
path('<str:user_id>/', UserIdView.as_view(), name='user-id')
]

Expand Down
72 changes: 7 additions & 65 deletions user_management/src/user/views/sign_up.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import json

from django.conf import settings
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from user.models import User
from user_management.JWTManager import UserRefreshJWTManager
from user_management.utils import (is_valid_email, is_valid_password,
is_valid_username)


@method_decorator(csrf_exempt, name='dispatch')
Expand All @@ -31,16 +32,17 @@ def post(self, request):
except Exception as e:
return JsonResponse(data={'errors': [f'An unexpected error occurred : {e}']}, status=500)

def signup_infos_validation(self, json_request):
@staticmethod
def signup_infos_validation(json_request):
validation_errors = []

username = json_request.get('username')
email = json_request.get('email')
password = json_request.get('password')

valid_username, error_message_username = self.is_valid_username(username)
valid_email, error_message_email = self.is_valid_email(email)
valid_password, error_message_password = self.is_valid_password(password)
valid_username, error_message_username = is_valid_username(username)
valid_email, error_message_email = is_valid_email(email)
valid_password, error_message_password = is_valid_password(password)

if not valid_username:
validation_errors.append(error_message_username)
Expand All @@ -49,63 +51,3 @@ def signup_infos_validation(self, json_request):
if not valid_password:
validation_errors.append(error_message_password)
return validation_errors

@staticmethod
def is_valid_username(username):
if username is None or username == '':
return False, 'Username empty'
if len(username) < settings.USERNAME_MIN_LENGTH:
return False, f'Username length {len(username)} < {settings.USERNAME_MIN_LENGTH}'
if len(username) > settings.USERNAME_MAX_LENGTH:
return False, f'Username length {len(username)} > {settings.USERNAME_MAX_LENGTH}'
if not username.isalnum():
return False, 'Username must be alphanumeric'
users = User.objects.filter(username=username)
if users.exists():
return False, f'Username {username} already taken'
return True, None

@staticmethod
def is_valid_email(email):
if email is None or email == '':
return False, 'Email empty'
users = User.objects.filter(email=email)
if users.exists():
return False, f'Email {email} already taken'
if len(email) > settings.EMAIL_MAX_LENGTH:
return False, f'Email length {len(email)} > {settings.EMAIL_MAX_LENGTH}'
if any(char in '!#$%^&*()=' for char in email):
return False, 'Invalid character in email address'
if '@' not in email:
return False, 'Email missing @'
if '.' not in email:
return False, 'Email missing "." character'
if email.count('@') > 1:
return False, 'Email contains more than one @ character'
local_part, domain_and_tld = email.rsplit('@', 1)
if len(local_part) < settings.EMAIL_LOCAL_PART_MIN_LENGTH:
return False, f'Local part length {len(local_part)} < {settings.EMAIL_LOCAL_PART_MIN_LENGTH}'
if domain_and_tld.count('.') == 0:
return False, 'Email missing TLD'
tld = domain_and_tld.rsplit('.')[-1]
if len(tld) > settings.TLD_MAX_LENGTH:
return False, f'TLD length {len(tld)} > {settings.TLD_MAX_LENGTH}'
return True, None

@staticmethod
def is_valid_password(password):
if password is None or password == '':
return False, 'Password empty'
if len(password) < settings.PASSWORD_MIN_LENGTH:
return False, f'Password length {len(password)} < {settings.PASSWORD_MIN_LENGTH}'
if len(password) > settings.PASSWORD_MAX_LENGTH:
return False, f'Password length {len(password)} > {settings.PASSWORD_MAX_LENGTH}'
if not any(char.isupper() for char in password):
return False, 'Password missing uppercase character'
if not any(char.islower() for char in password):
return False, 'Password missing lowercase character'
if not any(char.isdigit() for char in password):
return False, 'Password missing digit'
if not any(char in '!@#$%^&*()-_+=' for char in password):
return False, 'Password missing special character'
return True, None
Loading

0 comments on commit 89d5a52

Please sign in to comment.