-
Notifications
You must be signed in to change notification settings - Fork 39
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 read-only auth #373
implement read-only auth #373
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
|
957959c
to
6c570c8
Compare
#[serde(rename = "none")] | ||
Nothing, | ||
#[serde(rename = "readonly")] | ||
#[serde(rename = "read-only")] |
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.
This matches the types in the JS client library
pub struct AuthDocRequest { | ||
#[serde(default = "Authorization::full")] | ||
pub authorization: Authorization, | ||
#[serde(rename = "userId")] | ||
pub user_id: Option<String>, | ||
#[serde(default)] | ||
pub metadata: HashMap<String, Value>, |
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.
We don't use this at present. And if we did, we'd likely need to encode it into the token which we'd like to avoid since its length is unbounded. Let's just drop this field altogether.
@@ -440,37 +431,50 @@ async fn get_doc_as_update( | |||
async fn get_doc_as_update_deprecated( | |||
Path(doc_id): Path<String>, | |||
State(server_state): State<Arc<Server>>, | |||
authorization: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>, | |||
auth_header: Option<TypedHeader<headers::Authorization<headers::authorization::Bearer>>>, |
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.
A lot of these changes are just renaming vars/args to better differentiate between an Authorization (an enum that indicates read/write access) and an auth header.
Err(sync::Error::PermissionDenied { | ||
reason: "Token does not have write access".to_string(), | ||
}) |
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.
Do we plan to make the clients aware of the authorization level as well? Not sure how these errors are logged/captured/etc but as of now this is an expected code path, since clients will send updates as though they have full write access.
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.
Yeah, this just drops out before writing, and the resulting error gets logged in the y-sweet server. It shouldn't interrupt the websocket connection. I think we'll want to include the level of Authorization
(full or read-only) in the ClientToken
so that clients can use it when rendering the UI. I'm not sure how easy it is for the YSweetProvider
to interrupt local changes to the YDoc, but we could at least have the provider subscribe to changes on the YDoc and log warnings when in read-only mode.
0ad45b0
to
0fd37a9
Compare
|
||
/// The authorization level of the client. | ||
#[serde(rename = "authorization")] | ||
pub authorization: Authorization, |
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.
We're now returning authorization
as part of the ClientToken
so that clients can limit front-end functionality (or just render differently) when the token is read-only
.
…tion::Nothing variant
…and to pull it from a token in full-server mode
45d6f00
to
e80aea0
Compare
@@ -385,7 +385,7 @@ impl Server { | |||
if let Some(token) = token { | |||
let authorization = authenticator | |||
.verify_doc_token(token, doc, current_time_epoch_millis()) | |||
.map_err(|e| (StatusCode::FORBIDDEN, e))?; | |||
.map_err(|e| (StatusCode::UNAUTHORIZED, e))?; |
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.
is this a case where the test is wrong rather than the implementation? I think if the token is provided which it seems like the guard checks here), but the token is invalid, it's a forbidden rather than unauthorized.
@ellipsis-dev do you know?
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.
I think if verify_doc_token
fails, it's because the token is invalid - which should return a 401 - a failure to authenticate. (I hate that the spec calls it 401 Unauthorized
since 403s should be used when authorization fails while 401s should be used when authentication fails. 🤷♂️)
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.
From MDN docs:
The HTTP 401 Unauthorized
client error response status code indicates that a request was not successful because it lacks valid authentication credentials for the requested resource.
A 401 Unauthorized
is similar to the 403 Forbidden
response, except that a 403 is returned when a request contains valid credentials, but the client does not have permissions to perform a certain action.
The read-only auth implementation adds a |
Read-only auth uses a token-based system with |
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 for this! LGTM
Have left a few comments on this PR to give more context. Probably easiest to review this PR commit-by-commit, leveraging the context given in each commit message.
related: #307