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 read-only auth #373

Merged

Conversation

rolyatmax
Copy link
Member

@rolyatmax rolyatmax commented Jan 15, 2025

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

Copy link

vercel bot commented Jan 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
y-sweet-debugger ⬜️ Ignored (Inspect) Visit Preview Jan 17, 2025 5:13pm
y-sweet-demos ⬜️ Ignored (Inspect) Visit Preview Jan 17, 2025 5:13pm
y-sweet-gendocs ⬜️ Ignored (Inspect) Visit Preview Jan 17, 2025 5:13pm

@rolyatmax rolyatmax force-pushed the taylor/dis-3111-y-sweet-server-implement-read-only-auth branch from 957959c to 6c570c8 Compare January 15, 2025 21:31
#[serde(rename = "none")]
Nothing,
#[serde(rename = "readonly")]
#[serde(rename = "read-only")]
Copy link
Member Author

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>,
Copy link
Member Author

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>>>,
Copy link
Member Author

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.

Comment on lines +189 to +191
Err(sync::Error::PermissionDenied {
reason: "Token does not have write access".to_string(),
})
Copy link
Contributor

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.

Copy link
Member Author

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.


/// The authorization level of the client.
#[serde(rename = "authorization")]
pub authorization: Authorization,
Copy link
Member Author

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.

@@ -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))?;
Copy link
Member

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?

Copy link
Member Author

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. 🤷‍♂️)

Copy link
Member Author

@rolyatmax rolyatmax Jan 17, 2025

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.

Copy link
Contributor

ellipsis-dev bot commented Jan 17, 2025

The read-only auth implementation adds a ReadOnly permission level alongside Full access. It uses the Authorization enum for permission levels (api_types.rs) and enforces them through token-based authentication. Read-only tokens are generated with Authorization::ReadOnly and verified during API requests. The implementation includes proper serialization with "read-only" and "full" string representations (auth.rs).

Copy link
Contributor

ellipsis-dev bot commented Jan 17, 2025

Read-only auth uses a token-based system with ReadOnly and Full levels (Authorization enum in api_types.rs). Tokens are generated with gen_doc_token() and verified during access. The ClientToken structure includes the token and authorization level (api_types.rs). Read-only auth is tested (auth.rs).

Copy link
Member

@paulgb paulgb left a 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

crates/y-sweet-core/src/api_types.rs Outdated Show resolved Hide resolved
crates/y-sweet-core/src/api_types.rs Outdated Show resolved Hide resolved
@rolyatmax rolyatmax merged commit 5032427 into main Jan 17, 2025
11 checks passed
@rolyatmax rolyatmax deleted the taylor/dis-3111-y-sweet-server-implement-read-only-auth branch January 17, 2025 17:17
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.

3 participants