Skip to content

Conversation

@arslanather
Copy link
Contributor

Description

  • Added /api/v1/users/login/email POST endpoint.
  • Uses email and password to login user.
  • Example JSON:

Link to the associated Taiga ticket: https://projects.arbisoft.com/project/arbisoft-sessions-portal-20/us/262

Considerations

  • Ensure that the changes are merged only after receiving at least two reviews.
  • Take performance issues into account.
  • Verify that database migrations are backwards-compatible.

Post-review

Combine commits into distinct sets of changes.

@arslanather arslanather self-assigned this Jan 4, 2026
Comment on lines 118 to +158
@extend_schema(
responses={200: {"type": "string", "example": "Hello World"}}
request=EmailLoginSerializer,
responses={
200: {
"type": "object",
"properties": {
"refresh": {
"type": "string",
"description": "JWT refresh token",
"example": "eyJ0eXAiOiJKV1QiLCJhbGc..."
},
"access": {
"type": "string",
"description": "JWT access token",
"example": "eyJ0eXAiOiJKV1QiLCJhbGc..."
},
"user_info": {
"type": "object",
"properties": {
"full_name": {
"type": "string",
"example": "John Doe"
},
"first_name": {
"type": "string",
"example": "John"
},
"last_name": {
"type": "string",
"example": "Doe"
},
"avatar": {
"type": ["string", "null"],
"format": "uri",
"example": None,
"description": "User avatar URL, can be null"
}
}
}
}
},
Copy link
Member

Choose a reason for hiding this comment

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

Consider adding a response serializer (e.g., LoginResponseSerializer) instead of manually constructing the response dictionary and documenting it inline in @extend_schema.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this is not a serialized model and involves custom fields and calculations, I believe keeping it as is would be better for maintainability and readability.

Comment on lines 182 to 186
user = user_model.objects.filter(email=email).first()
if not user:
raise ValidationError("Invalid email or password")
if not user.check_password(password):
raise ValidationError("Invalid email or password")
Copy link
Member

Choose a reason for hiding this comment

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

[question] Why aren't we using Django's authenticate() method here? The current implementation is prone to time attack (The user lookup and password check happen separately which could leak the information about which emails exist)

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’ve updated the implementation. The check for the password was originally used because Django’s authentication backend relies on the username rather than the email. I’ve now applied a workaround and updated the usage to address the previously mentioned vulnerabilities.

Copy link
Member

@sameeramin sameeramin left a comment

Choose a reason for hiding this comment

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

Great work! Just a few suggestions, which I added in the comments

Copy link
Contributor

@Palwisha-18 Palwisha-18 left a comment

Choose a reason for hiding this comment

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

Minor Suggestions. Otherwise looks good. 👍


# Django's default authentication backend uses the `username` field, not `email`.
# We first fetch the user by email, then authenticate using their username.
user = user_model.objects.filter(email=email).first()
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Since email is unique, prefer get() over filter().first() to make the assumption explicit and fail fast if data integrity is violated.

'full_name': user.get_full_name(),
'first_name': user.first_name,
'last_name': user.last_name,
'avatar': None
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: avatar is always returned as None. If no avatar is available, consider omitting the field rather than returning a constant null value.

return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
'user_info': {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Consider including a stable identifier (e.g. user_id) in user_info to simplify client-side user state management.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants