mimickal / funboxusers Goto Github PK
View Code? Open in Web Editor NEWA lightweight user account system
A lightweight user account system
When we change email, we send an email to the new address. If the server running fbusers doesn't have a mail server configured, fbusers will encounter an error which propagates to the user as a 500.
We should detect when the mail server isn't configured, print (or log) a warning, then gracefully avoid trying to send emails.
If we want to be really slick, we could also write a "send email" mock that just logs the email to the console instead of sending it when testing manually. We could toggle this on with an environment variable like 'FB_DEV_MODE=Y' or something.
The backend endpoint that accepts the reset code and serves the reset page. This should be a GET endpoint so it works on-click in browsers. This endpoint should be rate-limited to prevent brute forcing password resets.
All requests should happen over https only. Never http.
Also, enable secure for login cookies
There is certainly a better way to ask the system for its hostname. Worst case we can add it to the config, but I would much rather a native function, or at least a shell call to hostname --fqdn
Apparently pyyaml is not a given for every distribution. This causes dependency issues in some cases.
This can be fixed by adding pyyaml
to requirements.
We have codes for emails, passwords, and now login sessions too. The database should be generalized to handle this without needing to add special code cases all the time.
Email codes should not store the email in the code itself. Put it somewhere else, like on the user (Will need 'email' and 'email_verified' field added to user)
Should only have one set of functions for CRUD operations on codes
Currently the backend endpoint doesn't check for a valid session when updating the email (it checks authorization instead). It should check the session and make sure it's valid. This should also be CSRF protected.
Depends on #48
Our current database code isn't very robust.
We extract rows to objects ourselves when the SQLite package provides this functionality.
We also cache the database connection at the module level, which can potentially lead to data corruption (as per @gbMichelle's review comment). We should instead create a connection per application context, as seen here http://flask.pocoo.org/docs/1.0/patterns/sqlite3/
Use the Err icon. Should probably be a .ico
Backend endpoint for performing the password reset operation. This should be a PUT request.
Verify fancy javascript version login page works and displays properly on the following browsers.
Required:
Nice to haves:
Flask has some nice built-in things for CSRF. Let's actually use them. https://flask-wtf.readthedocs.io/en/stable/csrf.htmlhttps://flask-wtf.readthedocs.io/en/stable/csrf.html
Frontend page for resetting a password. Should have the usual CSRF protection. Simply asks for a new password, with a confirm field. Should also have some javascript making sure the passwords match. On submission, sends the password change request with the one-time-use code to the backend.
Backend endpoint for triggering a reset of an account password. This is to allow users who are not logged in to ask the server for a password reset link. The link will be sent to them via email (but we'll handle the email portion in another task). Requires a valid user or email to perform this. Should be a POST request.
Backend endpoint for removing an email from an account. Requires the user to be logged in (so should check the session token). Should probably be a DELETE. Should also require CSRF protection.
Depends on #48
I don't want to start refactoring until we have some solid features in place, so for now, this list will just keep track of issues I'd like to see fixed at some point. If any one thing gets too large, I'll create a separate issue for it and link it here.
That means don't work on stuff in this issue until I create a separate issue and assign it to someone.
makeUniqueCode
should probably return the actual code objectCode.use_code
should work at the instance level too, as in mycode.use_code
. If this is a pain, maybe just call the instance version mycode.use()
.Backend endpoint to allow a logged in user to change their password. This should require both the old password to verify, and the new password to change it to. This should not require an email on the account.
Currently, when trying to update or add an email, we attempt to create an entry in the PendingEmail table. However, we do not check if a pending record already exists for the user, so trying to add a second email will lead to a 500 due to a DB uniqueness violation.
We should update the record if it already exists, or create it otherwise (also known as "upsert"). Since this endpoint sends an email with a confirmation code, the code for the pending record will have to change too, so that email link is no longer valid. The old code should be invalidated.
Unittest kinda sucks and requires a lot of boilerplate to do basic behavioral tests. The runner and output also suck. Let's move to something cool, like pocha.
As a proof of concept, let's just port one of the simpler test classes. test_add_user.py
is a good place to start. Remember to add the pocha test dependency to requirements.txt
too.
Backend endpoint for marking an account as deleted.
We probably want to perform a soft delete here (i.e. mark as deleted, not actually removing the record) to keep reference integrity.
Update: Let's support hard (delete entire row) and soft (mark as deleted, keep row) deletes, with an option for "zero" (zero out other fields before deleting).
We could also maybe support an option for "recursive" in the future that goes through and breaks any references to this account, but that's for another day.
Don't forget to not return soft-deleted accounts (and tests for this functionality, which probably should go under "get account" rather than "delete account").
Will also need to test every endpoint that deals with users, like PasswordReset and EmailConfirm codes.
If a user has successfully logged in and clears their cookies, attempting to log in again causes a 500, preventing the user from logging in again.
peewee.IntegrityError: UNIQUE constraint failed: logincode.user_id
If a user has an email on file, they should be alerted via that email address when their password is changed.
We currently have unit tests that show us that code works as intended when used in the ways we expect. But we don't know what code paths are actually ran.
We should set up a code coverage reporter to see if there is any possible paths that we've missed and to see if there is specific edge cases that we're not testing for.
The session cookie contains the current csrf token. Can we just read this token out of the cookie instead of setting it in a JSON header or hidden form field?
When inserting code association rows, we require passing in the code's string value, not the code object itself. This is counter-intuitive and leads to a lot of subtle, frustrating issues. Can we override the BaseCode
class to return the code value by default, kind of like how other objects return the primary key when printing?
Instead of this: LoginCode.create(user=user, code=mycode.code)
I want to do this: LoginCode.create(user=user, code=mycode)
Also, visiting account page without active login should redirect you to the login page.
Logging out should redirect to login page.
This should be enforced at the server level, not the client level, since the server can decide to just not serve a page.
The login cookie should expire after some time. The cookie should also have the various fields (httponly, secure, etc...) set properly.
Right now getUser endpoint returns the current confirmed email, or the pending email, or none, in that order. A user's original email should not be overridden until the pending email is confirmed. Therefore, this endpoint should return both the current email and the pending email.
Page for requesting a password reset for a user. Should accept email or username for the request. On submission always show success even if the user or email doesn't exist. Submit to the password reset trigger endpoint. Login page should link to this page.
Depends on #39
Right now when we ask a user to put in a new password we're only checking if the field exists in the form. We are not however checking if the password strings are empty or if the password matches up to the requirements that the server may have set.
We should create a function to validate passwords based on settings that the admin can configure. And use this whenever a user creates a new password.
Examples would be:
We currently set a placeholder string for the login cookie. Let's generate an actual session cookie and store / return that instead.
Any time a user is retrieved or modified, accessed_at should be updated to datetime.now()
We should log events in the system. For now, let's just log events, including 200, 400, and 500 responses. We should also log error stack traces and the names of the users they occurred for. This will help a lot in manual UI testing as well.
In order to reset a password, we will send the user an email containing a one-time-use code. The backend needs a way to represent the relationship between this code and its use for resetting passwords. We'll probably end up needing to add a "PasswordReset" table for this. The code should expire after some time.
In this task
When a user attempts to log in and the server finds an active token (like the situation in #77), maybe we should flash a little message on the login (or account) page. Nothing should functionally change, but it might be valuable for the user to know what happened. We could potentially add something about "Maybe your cleared your cookies?"
Not sure if this is valuable.
Actually send the user an email containing the password reset link
Depends on #22
makeUniqueCode checks the database for code uniqueness, but doesn't add the code. We rely on the calling logic to add the code, mostly because we have multiple code types (which we shouldn't).
Once codes are made generic, makeUniqueCode should also add the code to the database when creating it. This means user_id should also become nullable and updated after the fact.
This is probably better done as an admin page, or at least as a better script. The current implementation is really crap and involves plain-text passwords.
Users need to be able to actually log out.
This should probably be its own endpoint that invalidates the user's login session cookie. The browser itself should be instructed to "forget" the session information too. The UI link to do this should probably be on the account overview page.
When testing on our Debian server this test repeatedly fails because the time difference between created_at and the time comparing is consistently above (35 seconds) the default 5 second range for assertDateNearNow.
We have with app.app_context()
all over the place for database things right now. We should see if an ORM like SQLAlchemy can reduce some of this noise.
We currently send the email confirmation to the new email address when updating an email. We should also send that same confirmation to the old email before overwriting it, as a security measure. Obviously we won't do this when adding a new email.
What good is a users system without some way of knowing who is allowed to do what? We should have a database table for permissions, as well as a pivot table for knowing who has those permissions.
The login code should be persisted to the database. This would also allow us to look up the user info from the code alone.
The backend endpoint for retrieving account information. This should return the logged in user's account info (and nobody else's). Probably use the session token for this. Should return data containing:
Create the web page to display account info and change account settings. We should probably wait to do this until we have all the backend account endpoints complete.
Also, I'm giving this to myself because I've already almost got this page done.
Page should show:
Page should allow:
Right now we hard code 'Funbox' at the top of server.py
and use that as the server's name in emails. That should be read in from the config file instead.
People shouldn't be able to hammer on the server with failed login attempts, so let's rate limit failed login attempts from a client.
This should probably track something like IP so a bad actor can't lock a user out of their account by spamming invalid logins.
If we get an error that prematurely ends a request handler, any previously created database records currently stay created. This behavior was observed with the change email endpoint, where the server would fail to send an email and send a 500, but would still create the pending email record. Reloading the page and trying to change the email again results in a different 500, this time occurring because of a unique constraint violation in the PendingEmail table. Doing these things in a database transaction would prevent these partial records from being created.
Currently if one server test fails, every subsequent test automatically fails in the beforeEach step because of a database uniqueness violation. I suspect this is because pocha doesn't run the cleanup step if a test fails.
We can work around this by either making it always run that cleanup step (which it should anyway, this might be a pocha bug), or just dumping the database between runs.
It might make sense to use an in-memory database for testing. That would make it easy to dump everything between tests.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.