Resolves#53
This fixes a bug I accidentally introduced by misunderstanding the echo session package. I thought that calling session.Get would return an error if no session existed for the session token valule. It seems that instead, if a session doesn't exist, session.Get creates one on-demand.
To fix this, we have to check the IsNew field of the session to see if calling session.Get created this session on-demand or if this was a session that was previously created in the Create function.
I introduced this bug in #43.
fusion's current password mechanism is vulnerable to a timing attack:
https://en.wikipedia.org/wiki/Timing_attack
Because fusion checks passwords using simple character-by-character string comparison, a password attempt that begins with the correct characters will take longer to evaluate than one that starts with incorrect characters. For example, if the correct password is 'platypus123' then a password attempt of 'plates' will take longer to evaluate than 'spoons' because 'plates' and 'platypus' share a common prefix. An attacker who attempts the password 'plates' will know that they likely have the correct prefix.
To prevent the timing attack, this change hashes the user's password using PBKDF2 and compares hashes using subtle.ConstantTimeCompare, which is specifically designed to prevent timing attacks.
Currently, if the user logs in to the server with the correct password, they get a cookie called session-token. If the server's password changes, the server (correctly) responds with an HTTP 401 Unauthorized when the user tries to use the token associated with the old password.
The problem is that the server's HTTP 401 response doesn't tell the client's browser to delete the old session token cookie, so it keeps sending it on every request even though the server knows it's bad.
Currently, it doesn't cause any noticeable problems, but I'm working on a change that will affect the data that we store in the session token, so it can lock the user out even if they have the same password because the password will be correct but they'll be using a session token that's invalid.
It feels a bit messy that the entire program has write access to the configuration as a shared global object. Shared globals make it more difficult to reason about a program's behavior.
This rewrite reduces the problem a bit by making the shared global state read-only after the client calls conf.Load.
fusion's existing session handling logic is a bit incorrect but it still works due to workarounds.
sessions.NewCookieStore is supposed to receive a secure, random value:
https://pkg.go.dev/github.com/gorilla/sessions#section-readme
fusion currently passes it a fixed string baked into source. The result is that an attacker can forge a valid session token (they know the secret key) and send it to a fusion server, and the server would accept it.
The reason this isn't a problem right now is that fusion works around this by storing an additional copy of the password in the session and then checking the password on each authenticated request.
The current implementation works, but I think it's a bit more dangerous and complicated than what's ideal. Ideally, an attacker shouldn't be able to forge a session token at all.
This change makes it so that the session cookie store derives session cookies using the server password as the secure secret. That way, an attacker can't forge session tokens unless they know the server password, but if they know the server password, it's game over anyway.
Because we can trust the session store to reject requests with invalid session tokens, we don't need to store additional copies of the server password. We can trust that if the request has a valid session token, it's a token that the server created.
We are currently ignoring the error when we retrieve the session for a given HTTP cookie. I don't see any reason to ignore the error, as the value of the Session object is undefined unless err == nil.
This change updates the code so that we return an error if session.Get returns a non-nil error code.
We're using a 'magic string' for the echo session key name, which makes it easy for the different instances of the string to go out of sync. Using a named constant makes the intent clear and ensures all copies of the key name in the code stay in sync.