-
Notifications
You must be signed in to change notification settings - Fork 274
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
Include project code into project authentication token #802
Conversation
Wow, good work, it's more involved than I thought! It looks good, I just have two comments:
|
To explain more: the serializer only signs the data, it does not provide any confidentiality. So it should be fairly easy to extract the payload from a token. If you include the private code in the payload, it reveals it. Using the private code as salt could work, but it's not the intended use. I would simply concatenate the global secret key and the project's private code, and use that as the "secret key" passed to the serializer. The private code probably needs conversion to bytes or something. |
@zorun commented on 14 juil. 2021 à 19:01 UTC+2:
OK, you're right, I think I can do a much better job here. I was focused on how to do it, and forgot about what data I should use to feed the serializer. You should remember that the code is hashed, so you have only a string like
Totally. |
Ah right, this explains why the token size grows so much. Another reason not to include the (hashed) private code in the payload :) |
@zorun commented on 14 juil. 2021 à 19:11 UTC+2:
Well, it is possible, but using def generate_token(self, token_type="auth"):
"""Generate a timed and serialized JsonWebToken
:param token_type: Either "auth" for authentication (invalidated when project code changed),
or "reset" for password reset (invalidated after expiration)
"""
if token_type == "reset":
serializer = URLSafeTimedSerializer(
current_app.config["SECRET_KEY"], salt=token_type
)
token = serializer.dumps({"project_id": self.id})
else:
serializer = URLSafeSerializer(
current_app.config["SECRET_KEY"] + self.password, salt=token_type
)
token = serializer.dumps({"project_id": self.id, "password": self.password})
return token
@staticmethod
def verify_token(token, token_type="auth", max_age=3600):
"""Return the project id associated to the provided token,
None if the provided token is expired or not valid.
:param token: Serialized TimedJsonWebToken
:param token_type: Either "auth" for authentication (invalidated when project code changed),
or "reset" for password reset (invalidated after expiration)
:param max_age: Token expiration time (in seconds). Only used with token_type "reset"
"""
loads_kwargs = {}
if token_type == "reset":
serializer = URLSafeTimedSerializer(
current_app.config["SECRET_KEY"], salt=token_type
)
loads_kwargs["max_age"] = max_age
else:
serializer = URLSafeSerializer(
current_app.config["SECRET_KEY"], salt=token_type
)
(valid, data) = serializer.loads_unsafe(token)
if not valid and data is not None:
project = Project.query.get(data.get("project_id", None))
if project is not None:
# Recreate serializer with correct key
serializer = URLSafeSerializer(
current_app.config["SECRET_KEY"] + project.password,
salt=token_type,
)
try:
data = serializer.loads(token, **loads_kwargs)
except SignatureExpired:
return None
except BadSignature:
return None
return data.get("project_id") Tests are passing with this code. But honestly, I don't know if loading unsafe data and passing it to SQLAlchemy is dangerous or not. It's possible to run extra check to ensure that the unsafely loaded project id can be a valid project id, using a regexp or something like this. |
Yeah, this is something I didn't think about :( I also don't really like loading untrusted data this way... |
OK, now that every tests are OK, I would like to add a last change: reduce the token payload from Example:
In any case, old tokens from v4 are invalidated, so… |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I have added a few comments.
Don't forget to update the documentation, we have something about authenticating with a token in there.
And yes, I'm OK to shorten the payload. Actually, do we need the payload at all now?
I think so: the project name itself. Otherwise, it is an empty string valid for every project with the same password. OK, since password is hashed+salted this shouldn't happen, but... |
504dc43
to
2b3f527
Compare
Oh, that's interesting, thanks :-) I like the idea of changing the token length. Also, I feel that we could use shorter and more readable tokens by using another URL for them. What about something like |
2b3f527
to
6394aa6
Compare
Rebased for invitation templates. Ready for merging? |
@almet commented on 1 sept. 2021 à 20:49 UTC+2:
Arg, I missed that interesting comment. Using |
Fix spiral-project#780 The token used for shared links (aka authentication) includes the project password (aka code). This code is checked when reading the token. If the code is changed, the token is invalid. Test is included for that. Also, reset token are now using URLSafeTimedSerializer instead of the deprecated JWS provided by itsdangerous. The main change is that age is checked when verifying. The coolest effect is that warnings disappeared from tests.
… in URL This change is introduced to have the ability to invalidate auth token with password change. Token payload is still the same, but the key is the concatenation of SECRET_KEY project password. To have a clean verification, we need to have the project id before loading payload, to build the serializer with the correct key (including the password).
Add a test for valid token with invalid project_id query parameter
Keep it in a list, for forward compatibillity
6394aa6
to
6c0d0e2
Compare
I don't get why it's a problem? Why not, but the main idea was to shorten the URL to be friendlier, so adding |
OK, here's my tries:
They all work, so indeed, it was not a problem to make it shorter. |
By the way: using a different route for token will simplify the code. Now, |
Ah, since the token contains the project id, we might just want to use an URL like Also : what do you think about persisting some shorter tokens in the database, matching the project ID, to shorten the size of the URL? |
I am merging this PR as is, and open a new one for new path. It's another (even if desirable) goal. |
…t#802) Fix spiral-project#780 This a breaking change, the API for authentication is different, as it now requires `project_id`. Token is generated with only the project_id (so it's shorter than before), and signature is done by mixing password with secret key. Thus, it expires on every project code change.
Fix #780
The token used for shared links (aka authentication) includes the project password (aka code).
This code is checked when reading the token.
If the code is changed, the token is invalid. Test is included for that.
Also, reset token are now using URLSafeTimedSerializer instead of the deprecated JWS provided by itsdangerous.
The main change is that age is checked when verifying. The coolest effect is that warnings disappeared from tests.