diff --git a/backend/tests/sign_s3_objects_authz.rs b/backend/tests/sign_s3_objects_authz.rs index a2baf9d449..511ef39ab5 100644 --- a/backend/tests/sign_s3_objects_authz.rs +++ b/backend/tests/sign_s3_objects_authz.rs @@ -17,7 +17,11 @@ //! so two test functions sharing the one fixture workspace serve each other's stale — by then //! deleted — filesystem root. //! -//! Advanced S3 permissions are an enterprise feature, so this test requires the +//! A second test pins the `expiry_secs` bounds: the signature's `exp` follows the caller's +//! request, defaults to 12h, and is clamped to [60s, 7d]. It only mints signatures and never +//! fetches through the proxy, so it never populates or reads that cache. +//! +//! Advanced S3 permissions are an enterprise feature, so these tests require the //! `enterprise` + `private` + `parquet` features. #![cfg(all(feature = "enterprise", feature = "private", feature = "parquet"))] @@ -175,3 +179,88 @@ async fn test_sign_s3_objects_enforces_read_authz(db: Pool) -> anyhow: Ok(()) } + +/// `exp` is signed into the HMAC message, so the only way a caller can influence +/// it is through `expiry_secs` — pin the default and both clamp bounds. +#[sqlx::test(fixtures("base"))] +async fn test_sign_s3_objects_expiry_secs(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + let storage_dir = tempfile::tempdir()?; + configure_lfs(&db, &storage_dir.path().to_string_lossy()).await?; + + async fn signed_exp(base: &str, body: serde_json::Value) -> anyhow::Result { + let resp = authed( + client().post(format!("{base}/apps/sign_s3_objects")), + "SECRET_TOKEN", + ) + .json(&body) + .send() + .await?; + let status = resp.status(); + let signed: serde_json::Value = resp.json().await?; + assert!(status.is_success(), "sign must succeed: {status} {signed}"); + let presigned = signed[0]["presigned"] + .as_str() + .expect("sign must return a presigned string"); + let exp = presigned + .split('&') + .find_map(|kv| kv.strip_prefix("exp=")) + .expect("presigned string must carry exp"); + Ok(exp.parse::()?) + } + + let key = json!([{ "s3": "allowed/file.txt" }]); + // The handler stamps `now` itself, so assert on a window rather than an exact value. + // Keep the window well under the 60s lower bound, or an unclamped 1s would pass. + let ttl_around = |exp: i64| exp - chrono::Utc::now().timestamp(); + let tolerance = 30; + + let default_ttl = ttl_around(signed_exp(&base, json!({ "s3_objects": key.clone() })).await?); + assert!( + (43200 - tolerance..=43200).contains(&default_ttl), + "omitting expiry_secs must keep the 12h default, got {default_ttl}s" + ); + + let honored = ttl_around( + signed_exp( + &base, + json!({ "s3_objects": key.clone(), "expiry_secs": 300 }), + ) + .await?, + ); + assert!( + (300 - tolerance..=300).contains(&honored), + "expiry_secs must be honored verbatim inside the bounds, got {honored}s" + ); + + let clamped_low = ttl_around( + signed_exp( + &base, + json!({ "s3_objects": key.clone(), "expiry_secs": 1 }), + ) + .await?, + ); + assert!( + (60 - tolerance..=60).contains(&clamped_low), + "expiry_secs below 60s must clamp up to 60s, got {clamped_low}s" + ); + + let clamped_high = ttl_around( + signed_exp( + &base, + json!({ "s3_objects": key.clone(), "expiry_secs": 99_999_999 }), + ) + .await?, + ); + assert!( + (604800 - tolerance..=604800).contains(&clamped_high), + "expiry_secs above 7d must clamp down to 7d, got {clamped_high}s" + ); + + Ok(()) +} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 1fe3423316..d88c633106 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -13468,6 +13468,10 @@ paths: type: array items: $ref: "#/components/schemas/S3Object" + expiry_secs: + type: integer + format: int64 + description: how long the signature stays valid, in seconds. Defaults to 43200 (12h) and is clamped server-side to [60, 604800] (1 minute to 7 days). required: - s3_objects responses: diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 0e64d1cba2..7bc995844d 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -4112,10 +4112,18 @@ struct S3DeleteTokenClaims { pub exp: usize, } +#[cfg(feature = "parquet")] +const SIGN_S3_DEFAULT_EXPIRY_SECS: i64 = 12 * 60 * 60; +#[cfg(feature = "parquet")] +const SIGN_S3_MIN_EXPIRY_SECS: i64 = 60; +#[cfg(feature = "parquet")] +const SIGN_S3_MAX_EXPIRY_SECS: i64 = 7 * 24 * 60 * 60; + #[cfg(feature = "parquet")] #[derive(Deserialize)] struct S3TokenRequestBody { s3_objects: Vec, + expiry_secs: Option, } #[cfg(feature = "parquet")] async fn sign_s3_objects( @@ -4126,6 +4134,12 @@ async fn sign_s3_objects( ) -> Result>> { let workspace_key = get_workspace_key(&w_id, &db).await?; + let expiry_secs = body + .expiry_secs + .unwrap_or(SIGN_S3_DEFAULT_EXPIRY_SECS) + .clamp(SIGN_S3_MIN_EXPIRY_SECS, SIGN_S3_MAX_EXPIRY_SECS); + let exp = (chrono::Utc::now() + chrono::Duration::seconds(expiry_secs)).timestamp(); + let futures = body.s3_objects.into_iter().map(|s3_object| async { // The signature this mints is a transferable bearer capability: `validate_s3_signature` // only checks the HMAC and expiry, so anyone who obtains the string can read this key. @@ -4156,7 +4170,6 @@ async fn sign_s3_objects( ) .await?; - let exp = (chrono::Utc::now() + chrono::Duration::hours(12)).timestamp(); let message = format!( "file_key={}&exp={}{}", s3_object.s3.clone(), diff --git a/cli/src/guidance/skills.gen.ts b/cli/src/guidance/skills.gen.ts index 0f57c21463..b5cb3417f4 100644 --- a/cli/src/guidance/skills.gen.ts +++ b/cli/src/guidance/skills.gen.ts @@ -848,31 +848,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step @@ -1631,31 +1635,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step @@ -2508,31 +2516,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step @@ -4225,19 +4237,23 @@ def delete_s3_object(s3object: S3Object | str, s3_resource_path: str | None = No # # Args: # s3_objects: List of S3 objects to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed S3 objects -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object] +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object] # Sign a single S3 object for use by anonymous users in public apps. # # Args: # s3_object: S3 object to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed S3 object -def sign_s3_object(s3_object: S3Object | str) -> S3Object +def sign_s3_object(s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object # Generate presigned public URLs for an array of S3 objects. # If an S3 object is not signed yet, it will be signed first. @@ -4245,6 +4261,8 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Args: # s3_objects: List of S3 objects to sign # base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) +# expiry_secs: How long the signatures stay valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed public URLs @@ -4252,7 +4270,7 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Example: # >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] # >>> urls = client.get_presigned_s3_public_urls(s3_objs) -def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None) -> list[str] +def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None, expiry_secs: int | None = None) -> list[str] # Generate a presigned public URL for an S3 object. # If the S3 object is not signed yet, it will be signed first. @@ -4260,6 +4278,8 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Args: # s3_object: S3 object to sign # base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed public URL @@ -4267,7 +4287,7 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Example: # >>> s3_obj = S3Object(s3="/path/to/file.txt") # >>> url = client.get_presigned_s3_public_url(s3_obj) -def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None) -> str +def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None, expiry_secs: int | None = None) -> str # Get the current user information. # diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index 9400dabdc7..3c0ce050fb 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -31,6 +31,15 @@ logger = logging.getLogger("windmill_client") JobStatus = Literal["RUNNING", "WAITING", "COMPLETED"] +def _sign_s3_objects_body(s3_objects: list, expiry_secs: int | None) -> dict: + # `expiry_secs` is optional but not nullable in the spec, so omit it rather than + # sending an explicit null a validating gateway would reject. + body: dict = {"s3_objects": s3_objects} + if expiry_secs is not None: + body["expiry_secs"] = expiry_secs + return body + + class Windmill: """Windmill client for interacting with the Windmill API.""" @@ -1044,37 +1053,45 @@ class Windmill: except Exception as e: raise Exception("Could not delete file from S3") from e - def sign_s3_objects(self, s3_objects: list[S3Object | str]) -> list[S3Object]: + def sign_s3_objects( + self, s3_objects: list[S3Object | str], expiry_secs: int | None = None + ) -> list[S3Object]: """Sign S3 objects for use by anonymous users in public apps. Args: s3_objects: List of S3 objects to sign + expiry_secs: How long the signature stays valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: List of signed S3 objects """ return self.post( - f"/w/{self.workspace}/apps/sign_s3_objects", json={"s3_objects": list(map(parse_s3_object, s3_objects))} + f"/w/{self.workspace}/apps/sign_s3_objects", + json=_sign_s3_objects_body(list(map(parse_s3_object, s3_objects)), expiry_secs), ).json() - def sign_s3_object(self, s3_object: S3Object | str) -> S3Object: + def sign_s3_object(self, s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object: """Sign a single S3 object for use by anonymous users in public apps. Args: s3_object: S3 object to sign + expiry_secs: How long the signature stays valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: Signed S3 object """ return self.post( f"/w/{self.workspace}/apps/sign_s3_objects", - json={"s3_objects": [s3_object]}, + json=_sign_s3_objects_body([s3_object], expiry_secs), ).json()[0] def get_presigned_s3_public_urls( self, s3_objects: list[S3Object | str], base_url: str | None = None, + expiry_secs: int | None = None, ) -> list[str]: """ Generate presigned public URLs for an array of S3 objects. @@ -1083,6 +1100,8 @@ class Windmill: Args: s3_objects: List of S3 objects to sign base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) + expiry_secs: How long the signatures stay valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: List of signed public URLs @@ -1104,7 +1123,7 @@ class Windmill: if s3_objs_to_sign: signed_s3_objs = self.sign_s3_objects( - [s3_obj for s3_obj, _ in s3_objs_to_sign] + [s3_obj for s3_obj, _ in s3_objs_to_sign], expiry_secs ) for i, (_, original_index) in enumerate(s3_objs_to_sign): s3_objs[original_index] = parse_s3_object(signed_s3_objs[i]) @@ -1123,6 +1142,7 @@ class Windmill: self, s3_object: S3Object | str, base_url: str | None = None, + expiry_secs: int | None = None, ) -> str: """ Generate a presigned public URL for an S3 object. @@ -1131,6 +1151,8 @@ class Windmill: Args: s3_object: S3 object to sign base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) + expiry_secs: How long the signature stays valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: Signed public URL @@ -1139,7 +1161,7 @@ class Windmill: >>> s3_obj = S3Object(s3="/path/to/file.txt") >>> url = client.get_presigned_s3_public_url(s3_obj) """ - urls = self.get_presigned_s3_public_urls([s3_object], base_url) + urls = self.get_presigned_s3_public_urls([s3_object], base_url, expiry_secs) return urls[0] def _get_public_base_url(self) -> str: @@ -1814,27 +1836,38 @@ def delete_s3_object( @init_global_client -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object]: +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object]: """ Sign S3 objects to be used by anonymous users in public apps Returns a list of signed s3 tokens + + Args: + s3_objects: List of S3 objects to sign + expiry_secs: How long the signatures stay valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) """ - return _client.sign_s3_objects(s3_objects) + return _client.sign_s3_objects(s3_objects, expiry_secs) @init_global_client -def sign_s3_object(s3_object: S3Object| str) -> S3Object: +def sign_s3_object(s3_object: S3Object| str, expiry_secs: int | None = None) -> S3Object: """ Sign S3 object to be used by anonymous users in public apps Returns a signed s3 object + + Args: + s3_object: S3 object to sign + expiry_secs: How long the signature stays valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) """ - return _client.sign_s3_object(s3_object) + return _client.sign_s3_object(s3_object, expiry_secs) @init_global_client def get_presigned_s3_public_urls( s3_objects: list[S3Object | str], base_url: str | None = None, + expiry_secs: int | None = None, ) -> list[str]: """ Generate presigned public URLs for an array of S3 objects. @@ -1843,6 +1876,8 @@ def get_presigned_s3_public_urls( Args: s3_objects: List of S3 objects to sign base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) + expiry_secs: How long the signatures stay valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: List of signed public URLs @@ -1853,13 +1888,14 @@ def get_presigned_s3_public_urls( >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] >>> urls = wmill.get_presigned_s3_public_urls(s3_objs) """ - return _client.get_presigned_s3_public_urls(s3_objects, base_url) + return _client.get_presigned_s3_public_urls(s3_objects, base_url, expiry_secs) @init_global_client def get_presigned_s3_public_url( s3_object: S3Object | str, base_url: str | None = None, + expiry_secs: int | None = None, ) -> str: """ Generate a presigned public URL for an S3 object. @@ -1868,6 +1904,8 @@ def get_presigned_s3_public_url( Args: s3_object: S3 object to sign base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) + expiry_secs: How long the signature stays valid, in seconds + (defaults to 43200 = 12h, clamped to [60, 604800]) Returns: Signed public URL @@ -1878,7 +1916,7 @@ def get_presigned_s3_public_url( >>> s3_obj = S3Object(s3="/path/to/file.txt") >>> url = wmill.get_presigned_s3_public_url(s3_obj) """ - return _client.get_presigned_s3_public_url(s3_object, base_url) + return _client.get_presigned_s3_public_url(s3_object, base_url, expiry_secs) @init_global_client diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index d6fe7689f9..2c3b585c20 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -1546,31 +1546,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step @@ -2169,19 +2173,23 @@ def delete_s3_object(s3object: S3Object | str, s3_resource_path: str | None = No # # Args: # s3_objects: List of S3 objects to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed S3 objects -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object] +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object] # Sign a single S3 object for use by anonymous users in public apps. # # Args: # s3_object: S3 object to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed S3 object -def sign_s3_object(s3_object: S3Object | str) -> S3Object +def sign_s3_object(s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object # Generate presigned public URLs for an array of S3 objects. # If an S3 object is not signed yet, it will be signed first. @@ -2189,6 +2197,8 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Args: # s3_objects: List of S3 objects to sign # base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) +# expiry_secs: How long the signatures stay valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed public URLs @@ -2196,7 +2206,7 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Example: # >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] # >>> urls = client.get_presigned_s3_public_urls(s3_objs) -def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None) -> list[str] +def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None, expiry_secs: int | None = None) -> list[str] # Generate a presigned public URL for an S3 object. # If the S3 object is not signed yet, it will be signed first. @@ -2204,6 +2214,8 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Args: # s3_object: S3 object to sign # base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed public URL @@ -2211,7 +2223,7 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Example: # >>> s3_obj = S3Object(s3="/path/to/file.txt") # >>> url = client.get_presigned_s3_public_url(s3_obj) -def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None) -> str +def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None, expiry_secs: int | None = None) -> str # Get the current user information. # diff --git a/system_prompts/auto-generated/script.md b/system_prompts/auto-generated/script.md index 9b976e347f..0b67fa0027 100644 --- a/system_prompts/auto-generated/script.md +++ b/system_prompts/auto-generated/script.md @@ -1738,31 +1738,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step @@ -2361,19 +2365,23 @@ def delete_s3_object(s3object: S3Object | str, s3_resource_path: str | None = No # # Args: # s3_objects: List of S3 objects to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed S3 objects -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object] +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object] # Sign a single S3 object for use by anonymous users in public apps. # # Args: # s3_object: S3 object to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed S3 object -def sign_s3_object(s3_object: S3Object | str) -> S3Object +def sign_s3_object(s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object # Generate presigned public URLs for an array of S3 objects. # If an S3 object is not signed yet, it will be signed first. @@ -2381,6 +2389,8 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Args: # s3_objects: List of S3 objects to sign # base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) +# expiry_secs: How long the signatures stay valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed public URLs @@ -2388,7 +2398,7 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Example: # >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] # >>> urls = client.get_presigned_s3_public_urls(s3_objs) -def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None) -> list[str] +def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None, expiry_secs: int | None = None) -> list[str] # Generate a presigned public URL for an S3 object. # If the S3 object is not signed yet, it will be signed first. @@ -2396,6 +2406,8 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Args: # s3_object: S3 object to sign # base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed public URL @@ -2403,7 +2415,7 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Example: # >>> s3_obj = S3Object(s3="/path/to/file.txt") # >>> url = client.get_presigned_s3_public_url(s3_obj) -def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None) -> str +def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None, expiry_secs: int | None = None) -> str # Get the current user information. # diff --git a/system_prompts/auto-generated/sdks/python.md b/system_prompts/auto-generated/sdks/python.md index 0c5a0f37d7..f30f1a0018 100644 --- a/system_prompts/auto-generated/sdks/python.md +++ b/system_prompts/auto-generated/sdks/python.md @@ -313,19 +313,23 @@ def delete_s3_object(s3object: S3Object | str, s3_resource_path: str | None = No # # Args: # s3_objects: List of S3 objects to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed S3 objects -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object] +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object] # Sign a single S3 object for use by anonymous users in public apps. # # Args: # s3_object: S3 object to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed S3 object -def sign_s3_object(s3_object: S3Object | str) -> S3Object +def sign_s3_object(s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object # Generate presigned public URLs for an array of S3 objects. # If an S3 object is not signed yet, it will be signed first. @@ -333,6 +337,8 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Args: # s3_objects: List of S3 objects to sign # base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) +# expiry_secs: How long the signatures stay valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed public URLs @@ -340,7 +346,7 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Example: # >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] # >>> urls = client.get_presigned_s3_public_urls(s3_objs) -def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None) -> list[str] +def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None, expiry_secs: int | None = None) -> list[str] # Generate a presigned public URL for an S3 object. # If the S3 object is not signed yet, it will be signed first. @@ -348,6 +354,8 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Args: # s3_object: S3 object to sign # base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed public URL @@ -355,7 +363,7 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Example: # >>> s3_obj = S3Object(s3="/path/to/file.txt") # >>> url = client.get_presigned_s3_public_url(s3_obj) -def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None) -> str +def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None, expiry_secs: int | None = None) -> str # Get the current user information. # diff --git a/system_prompts/auto-generated/sdks/typescript.md b/system_prompts/auto-generated/sdks/typescript.md index df0ff4786f..1269a3e497 100644 --- a/system_prompts/auto-generated/sdks/typescript.md +++ b/system_prompts/auto-generated/sdks/typescript.md @@ -303,31 +303,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step diff --git a/system_prompts/auto-generated/skills/write-script-bun/SKILL.md b/system_prompts/auto-generated/skills/write-script-bun/SKILL.md index 9d19e4bf85..74ef8f1908 100644 --- a/system_prompts/auto-generated/skills/write-script-bun/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bun/SKILL.md @@ -474,31 +474,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step diff --git a/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md b/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md index 5803909032..0c7b6aecde 100644 --- a/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md @@ -474,31 +474,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step diff --git a/system_prompts/auto-generated/skills/write-script-deno/SKILL.md b/system_prompts/auto-generated/skills/write-script-deno/SKILL.md index 32691fdba8..930f078a58 100644 --- a/system_prompts/auto-generated/skills/write-script-deno/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-deno/SKILL.md @@ -476,31 +476,35 @@ async deleteS3File(s3object: S3Object, workspace: string | undefined = undefined /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ -async signS3Objects(s3objects: S3Object[]): Promise +async signS3Objects(s3objects: S3Object[], { expirySecs }: { expirySecs?: number } = {}): Promise /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -async signS3Object(s3object: S3Object): Promise +async signS3Object(s3object: S3Object, { expirySecs }: { expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ -async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrls(s3Objects: S3Object[], { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ -async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl }: { baseUrl?: string } = {}): Promise +async getPresignedS3PublicUrl(s3Objects: S3Object, { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {}): Promise /** * Get URLs needed for resuming a flow after this step diff --git a/system_prompts/auto-generated/skills/write-script-python3/SKILL.md b/system_prompts/auto-generated/skills/write-script-python3/SKILL.md index afa5eda299..274ffb343e 100644 --- a/system_prompts/auto-generated/skills/write-script-python3/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-python3/SKILL.md @@ -498,19 +498,23 @@ def delete_s3_object(s3object: S3Object | str, s3_resource_path: str | None = No # # Args: # s3_objects: List of S3 objects to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed S3 objects -def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object] +def sign_s3_objects(s3_objects: list[S3Object | str], expiry_secs: int | None = None) -> list[S3Object] # Sign a single S3 object for use by anonymous users in public apps. # # Args: # s3_object: S3 object to sign +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed S3 object -def sign_s3_object(s3_object: S3Object | str) -> S3Object +def sign_s3_object(s3_object: S3Object | str, expiry_secs: int | None = None) -> S3Object # Generate presigned public URLs for an array of S3 objects. # If an S3 object is not signed yet, it will be signed first. @@ -518,6 +522,8 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Args: # s3_objects: List of S3 objects to sign # base_url: Optional base URL for the presigned URLs (defaults to WM_BASE_URL) +# expiry_secs: How long the signatures stay valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # List of signed public URLs @@ -525,7 +531,7 @@ def sign_s3_object(s3_object: S3Object | str) -> S3Object # Example: # >>> s3_objs = [S3Object(s3="/path/to/file1.txt"), S3Object(s3="/path/to/file2.txt")] # >>> urls = client.get_presigned_s3_public_urls(s3_objs) -def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None) -> list[str] +def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str | None = None, expiry_secs: int | None = None) -> list[str] # Generate a presigned public URL for an S3 object. # If the S3 object is not signed yet, it will be signed first. @@ -533,6 +539,8 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Args: # s3_object: S3 object to sign # base_url: Optional base URL for the presigned URL (defaults to WM_BASE_URL) +# expiry_secs: How long the signature stays valid, in seconds +# (defaults to 43200 = 12h, clamped to [60, 604800]) # # Returns: # Signed public URL @@ -540,7 +548,7 @@ def get_presigned_s3_public_urls(s3_objects: list[S3Object | str], base_url: str # Example: # >>> s3_obj = S3Object(s3="/path/to/file.txt") # >>> url = client.get_presigned_s3_public_url(s3_obj) -def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None) -> str +def get_presigned_s3_public_url(s3_object: S3Object | str, base_url: str | None = None, expiry_secs: int | None = None) -> str # Get the current user information. # diff --git a/typescript-client/client.d.ts b/typescript-client/client.d.ts index 1fa1ed1ca6..1e4448300e 100644 --- a/typescript-client/client.d.ts +++ b/typescript-client/client.d.ts @@ -200,37 +200,45 @@ export declare function writeS3File( /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ export declare function signS3Objects( - s3objects: S3Object[] + s3objects: S3Object[], + { expirySecs }?: { expirySecs?: number } ): Promise; /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -export declare function signS3Object(s3object: S3Object): Promise; +export declare function signS3Object( + s3object: S3Object, + { expirySecs }?: { expirySecs?: number } +): Promise; /** * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ export declare function getPresignedS3PublicUrls( s3Objects: S3Object[], - { baseUrl }: { baseUrl?: string } + { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } ): Promise; /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ export declare function getPresignedS3PublicUrl( s3Objects: S3Object, - { baseUrl }: { baseUrl?: string } + { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } ): Promise; /** diff --git a/typescript-client/client.ts b/typescript-client/client.ts index f7be5e5a6d..105641eee4 100644 --- a/typescript-client/client.ts +++ b/typescript-client/client.ts @@ -1057,15 +1057,18 @@ export async function deleteS3File( /** * Sign S3 objects to be used by anonymous users in public apps * @param s3objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 objects */ export async function signS3Objects( - s3objects: S3Object[] + s3objects: S3Object[], + { expirySecs }: { expirySecs?: number } = {} ): Promise { const signedKeys = await AppService.signS3Objects({ workspace: getWorkspace(), requestBody: { s3_objects: s3objects.map(parseS3Object), + expiry_secs: expirySecs, }, }); return signedKeys; @@ -1073,10 +1076,14 @@ export async function signS3Objects( /** * Sign S3 object to be used by anonymous users in public apps * @param s3object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed s3 object */ -export async function signS3Object(s3object: S3Object): Promise { - const [signedObject] = await signS3Objects([s3object]); +export async function signS3Object( + s3object: S3Object, + { expirySecs }: { expirySecs?: number } = {} +): Promise { + const [signedObject] = await signS3Objects([s3object], { expirySecs }); return signedObject; } @@ -1084,11 +1091,12 @@ export async function signS3Object(s3object: S3Object): Promise { * Generate a presigned public URL for an array of S3 objects. * If an S3 object is not signed yet, it will be signed first. * @param s3Objects s3 objects to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns list of signed public URLs */ export async function getPresignedS3PublicUrls( s3Objects: S3Object[], - { baseUrl }: { baseUrl?: string } = {} + { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {} ): Promise { baseUrl ??= getPublicBaseUrl(); @@ -1100,7 +1108,8 @@ export async function getPresignedS3PublicUrls( .filter(([s3Obj, _]) => s3Obj.presigned === undefined); if (s3ObjsToSign.length > 0) { const signedS3Objs = await signS3Objects( - s3ObjsToSign.map(([s3Obj, _]) => s3Obj) + s3ObjsToSign.map(([s3Obj, _]) => s3Obj), + { expirySecs } ); for (let i = 0; i < s3ObjsToSign.length; i++) { const [_, originalIndex] = s3ObjsToSign[i]; @@ -1120,13 +1129,17 @@ export async function getPresignedS3PublicUrls( /** * Generate a presigned public URL for an S3 object. If the S3 object is not signed yet, it will be signed first. * @param s3Object s3 object to sign + * @param expirySecs how long the signature stays valid, in seconds (default 43200 = 12h, clamped to [60, 604800]) * @returns signed public URL */ export async function getPresignedS3PublicUrl( s3Objects: S3Object, - { baseUrl }: { baseUrl?: string } = {} + { baseUrl, expirySecs }: { baseUrl?: string; expirySecs?: number } = {} ): Promise { - const [s3Object] = await getPresignedS3PublicUrls([s3Objects], { baseUrl }); + const [s3Object] = await getPresignedS3PublicUrls([s3Objects], { + baseUrl, + expirySecs, + }); return s3Object; }