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

Implement login interface #77

Merged
merged 9 commits into from
Sep 26, 2022
Merged

Implement login interface #77

merged 9 commits into from
Sep 26, 2022

Conversation

jacobtylerwalls
Copy link
Contributor

@jacobtylerwalls jacobtylerwalls commented Sep 22, 2022

Overview

Implement login interface.

  • Expose endpoints for login, logout, and other standard endpoints from dj-rest-auth
  • Add login and logout views at api/auth/login and api/auth/logout
  • Redirect users to login page when logged out
  • Redirect users to their originally requested page upon successful login
  • Avoid forcing users to login again when refreshing the page

Closes #66

Demo

Screen Shot 2022-09-22 at 5 49 25 PM

% curl -X POST --data '{"email":"e","password":"p"}' -H "Content-Type:application/json" localhost:8181/api/auth/login/
{"email":["Enter a valid email address."]}

% curl -X POST --data '{"email":"a1@azavea.com","password":"p"}' -H "Content-Type:application/json" localhost:8181/api/auth/login/
{"non_field_errors":["Unable to log in with provided credentials."]}

% curl -X POST --data '{"email":"a1@azavea.com","password":"password"}' -H "Content-Type:application/json" localhost:8181/api/auth/login/

Notes

  • So far, I chose not to await the /login requests, so let me know if that is desired.
  • dj-rest-auth is the currently maintained fork of django-rest-auth, which will not be compatible with Django 4.
  • I opted for placeholder texts in the input boxes instead of labels, but I did add aria-labels
  • I figured it was harmless to retain the username field of the /login endpoint: it appears to work just fine (EDIT: well, it works using the dj-rest-auth endpoint, not the session-based django endpoint, and now only the django endpoint is exposed as of 8039f8f). Let me know if it's worth expending effort here to hide the username field.

Testing Instructions

  • ./scripts/update
  • ./scripts/server
  • Visit localhost:8181/draw
    • Redirected to login
    • Incorrect or missing credentials are rejected with an error message
    • Successful login reroutes you directly to /draw
  • Refresh while logged in
    • You are not rerouted to login screen
  • Simulate logout by clearing browser data, refresh page
    • You are rerouted to login screen
    • Successful login reroutes you to /welcome
  • Can log in with Enter key from username or password field
  • Test browsable API for django login and logout views at localhost:8181/api/auth/login and localhost:8181/api/auth/logout
  • Visit Django admin at localhost:8181/admin/api, ensure no additional auth-related entities present
  • Compare styling of login page to figma designs in Add Login Interface #66
  • Ensure login form follows accessibility standards

Checklist

  • fixup! commits have been squashed
  • CHANGELOG.md updated with summary of features or fixes, following Keep a Changelog guidelines
  • README.md updated if necessary to reflect the changes
  • CI passes after rebase

@jacobtylerwalls jacobtylerwalls marked this pull request as draft September 23, 2022 13:47
@jacobtylerwalls jacobtylerwalls marked this pull request as ready for review September 23, 2022 14:11
Copy link
Contributor

@mstone121 mstone121 left a comment

Choose a reason for hiding this comment

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

Looks good! I have few questions mostly regarding the front-end.

src/app/src/components/PrivateRoute.js Outdated Show resolved Hide resolved
src/app/src/App.js Outdated Show resolved Hide resolved
@@ -38,3 +38,8 @@ export const PANES = {
MUNICIPAL_BOUNDARY_LABELS: { label: 'muni-labels', zIndex: 225 },
USER_POLYGON: { label: 'user-polygon', zIndex: 490 },
};

export const URLS = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export const URLS = {
export const API_URLS = {

We might eventually want to extract the frontend route names to a constant in which case URLS might be confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

def post(self, request, *args, **kwargs):
logout(request)

return Response(status=status.HTTP_204_NO_CONTENT)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we split this file up like we did models.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.

@rajadain
Copy link
Contributor

Taking a look now

function App() {
return (
<BrowserRouter>
<Flex>
<Routes>
<Route path='/draw' element={<Sidebar />} />
<Route
path='/draw'
Copy link
Contributor

Choose a reason for hiding this comment

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

Interestingly, /draw does redirect me, but /draw/ (which I usually use) does not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is concerning: apparently, this is the request axios is making, which obviously fails because there are no endpoints exposed under /draw/.

GET http://localhost:4545/draw/api/auth/login/ 404 (Not Found)

I'll troubleshoot on Monday, but let me know if anything comes to mind immediately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, c97adef seemed to do it.

loginError: {
fontSize: 'md',
alignItems: 'center',
color: 'red',
Copy link
Contributor

Choose a reason for hiding this comment

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

This could use some design input from @gatesgodin. It currently isn't legible against the gray background:

image

Copy link
Contributor Author

@jacobtylerwalls jacobtylerwalls Sep 23, 2022

Choose a reason for hiding this comment

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

Also @gatesgodin if you have an opinion about using placeholder text for email and password instead of labels above the inputs I'm interested in that as well (see screenshot at top of PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@rajadain rajadain left a comment

Choose a reason for hiding this comment

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

This is looking great! There's a couple of design changes to go through, but great job overall.

Comment on lines 81 to 88
<Button
w='320px'
variant='cta'
onClick={() => loginRequest(email, password)}
>
Login
</Button>
<Button variant='link'>Forgot password?</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider wrapping these buttons in a smaller VStack since they should have less spacing between them

Suggested change
<Button
w='320px'
variant='cta'
onClick={() => loginRequest(email, password)}
>
Login
</Button>
<Button variant='link'>Forgot password?</Button>
<VStack spacing={2}>
<Button
w='320px'
variant='cta'
onClick={() => loginRequest(email, password)}
>
Login
</Button>
<Button variant='minimal'>Forgot password?</Button>
</VStack>

Where minimal is a new Button variant that matches the wireframes:

        minimal: {
            fontWeight: 400,
            fontSize: 'sm',
            textDecoration: 'none',
            color: 'gray.800',
        },

Copy link
Contributor Author

@jacobtylerwalls jacobtylerwalls Sep 23, 2022

Choose a reason for hiding this comment

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

@gatesgodin did we consider the possibility of underlining all text links in the app? My concern was that if we leave "Forgot password?" without underlining then we introduce a small inconsistency and a small accessibility/usability issue, but I'm persuadable, since "Forgot password?" is such a common web interaction.

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'm going to apply Terence's diff (thank you!), and we can always add underlining back later if desired.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

@jacobtylerwalls Hey! If we change the login page background to Grey-50 and the error message to Red-600 it will meet AAA color contrast requirements.

For the links, I'm open to underline the 'Forgot password'. There aren't many links within this app, but I do agree that they should be underlined if any surface throughout the development.

For the form labels, typically if there are 2 or less fields I opt to have the label as the placeholder for a cleaner look. Although if the form field has specific formatting (like a phone number) or many fields, then I would say that it's important to show the fields. Feel free to add them in if time permits.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, I used red-600 and gray-50 in 4ccf042.

I'll add the underlining back in. This is the only text link so far:
Screen Shot 2022-09-26 at 8 19 47 AM

src/app/src/pages/Login.js Outdated Show resolved Hide resolved
src/app/src/pages/Login.js Show resolved Hide resolved
Copy link
Contributor

@rajadain rajadain left a comment

Choose a reason for hiding this comment

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

This looks really great! Nice job getting all the little details in.

I would recommend considering the URL redirect for logging in. Once that is in, this should be good to go!

src/app/src/pages/Login.js Outdated Show resolved Hide resolved
@rajadain
Copy link
Contributor

Taking a final look

Copy link
Contributor

@rajadain rajadain left a comment

Choose a reason for hiding this comment

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

+1 tested, this looks really good! Great work on this, with all the fixup commits and being so responsive. Would recommend making sure the fixups merge correctly!

class Login(LoginView):
permission_classes = (AllowAny,)

@method_decorator(ensure_csrf_cookie)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this required here? Since we already have the middleware installed?

'django.middleware.csrf.CsrfViewMiddleware',

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was required with my first attempt at axios integration, but I'll give it another look if still needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the CSRF reference, it's not clear to me that the cookie is always sent with the response. Before posting the PR, I found that re-logging in at /login would sometimes fail, as the user was already in an authenticated session, and adding ensure_csrf_cookie fixed it. Later, using the correct axios integration seemed to take care of that, and plus--now you are redirected away from /login if you're logged in. So I'll take it out 👍🏻

if not request.user.is_active:
raise AuthenticationFailed("Unable to sign in")

return Response(status=status.HTTP_204_NO_CONTENT)
Copy link
Contributor

Choose a reason for hiding this comment

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

In the future we may change this to return some user details (like Role, etc) for the front-end. Fine for now.

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.

Add Login Interface
4 participants