Skip to content

Conversation

chimnayajith
Copy link
Contributor

@chimnayajith chimnayajith commented Aug 2, 2025

Implement getFileTemporaryUrl and tryGetFileTemporaryUrl to access user-uploaded files without requiring authentication. The temporary URLs remain valid for 60 seconds and provide a secure way to share files without exposing API keys.

This uses the GET /user_uploads/{realm_id}/{filename} endpoint that returns a URL allowing immediate access without requiring authentication.

Requested in #1144 (comment)

Will be needed for #42 and #43

@gnprice
Copy link
Member

gnprice commented Aug 3, 2025

Cool. This will also be the main ingredient in fixing #1732, which is an issue in our upcoming milestone M6.

(I haven't yet looked at this implementation at all.)

///
/// This endpoint is documented in the OpenAPI description:
/// https://github.com/zulip/zulip/blob/main/zerver/openapi/zulip.yaml
/// under the name `get_file_temporary_url`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// under the name `get_file_temporary_url`.
/// under the name `get-file-temporary-url`.

I couldn't find a match of get_file_temporary_url searching in zulip.yaml, but searching get-file-temporary-url has a match.

@@ -327,6 +327,51 @@ class UploadFileResult {
Map<String, dynamic> toJson() => _$UploadFileResultToJson(this);
}

/// Get a temporary, authless partial URL to a realm-uploaded file.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: commit summary line

- api [nfc]: Add functions to get temporary URLs for files
+ api: Add routes getFileTemporaryUrl and tryGetFileTemporaryUrl

The two functions in the commit introduce new behavior (new API calls and logic), so it's not an NFC commit. Also, good to mention the routes introduced in the summary line.

});
});

test('returns temporary URL for valid realm file', () {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test and the remaining test blocks in the group require an indentation.

realmUrl: connection.realmUrl);

check(result).isNull();
// Verify no API calls were made
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Verify no API calls were made
// Verify no API calls were made.

or

Suggested change
// Verify no API calls were made
// verify no API calls were made

Either complete a sentence that starts with a capital letter by adding a period at the end, or start it with a lowercase letter and omit the period. 🙂
(the same applies to the one below this)

test('returns null for non-realm URL', () {
return FakeApiConnection.with_((connection) async {
final result = await tryGetFileTemporaryUrl(connection,
url: Uri.parse('https://example.com/image.jpg'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
url: Uri.parse('https://example.com/image.jpg'),
url: Uri.parse('https://example.com/user_uploads/123/testfile.jpg'),

The URL provided is both non-realm URL and a non-matching URL, but the test case is only about non-realm URL, so it's good to adjust it to only what the test case is about. That way, it's easy to validate it against what the test case claims.

Copy link
Collaborator

@sm-sayedi sm-sayedi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chimnayajith for taking care of this. A few comments above!

@chrisbobbe
Copy link
Collaborator

@chimnayajith please post here when you've pushed a revision for Sayed's review; thank you :)

Implement `getFileTemporaryUrl` and `tryGetFileTemporaryUrl` to access
user-uploaded files without requiring authentication. The temporary URLs
remain valid for 60 seconds and provide a secure way to share files
without exposing API keys.

This uses the GET `/user_uploads/{realm_id}/{filename}` endpoint that
returns a URL allowing immediate access without requiring authentication.

Requested in zulip#1144 (comment)
@chimnayajith
Copy link
Contributor Author

@sm-sayedi Pushed a revision. PTAL.

@sm-sayedi
Copy link
Collaborator

Thanks, LGTM. Marking for Chris's review.

@sm-sayedi sm-sayedi requested a review from chrisbobbe August 24, 2025 09:26
@sm-sayedi sm-sayedi added the maintainer review PR ready for review by Zulip maintainers label Aug 24, 2025
@chrisbobbe
Copy link
Collaborator

Thanks @chimnayajith for building this, and @sm-sayedi for the reviews!

I found this in the API docs, by trying the string "get-file-temporary-url" that was helpfully included in a dartdoc :)

Here's the page: https://zulip.com/api/get-file-temporary-url

It doesn't have a link in the left sidebar, and I've started a topic for that in #api documentation: #api documentation > get-file-temporary-url in left sidebar @ 💬

@chrisbobbe
Copy link
Collaborator

I'd like to organize the code slightly differently than here (and in zulip-mobile), to make the API binding for this endpoint as thin a wrapper as possible.

In particular:

  • The new endpoint isn't really about a message, so instead of messages.dart, let's put it in a new file like lib/api/route/uploads.dart.
  • Make and use a class GetFileTemporaryUrlResult, following the pattern of GetMessageResult, GetStreamTopicsResult, etc.
  • Give getFileTemporaryUrl params realmIdStr and filename, corresponding to the documentation
  • Don't add a function tryGetFileTemporaryUrl
  • Instead, to serve getFileTemporaryUrl's callers, write a function in lib/model/internal_link.dart that produces values for realmIdStr and filename, by parsing an upload link. That can return null if parsing fails, and in that case we just won't call `getFileTemporaryUrl.

@chrisbobbe
Copy link
Collaborator

#1732 is another issue that would benefit from this logic; @chimnayajith please let us know if we can help unblock you on any of the work here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR ready for review by Zulip maintainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants