cloud_file_signer/aws/
mod.rs

1//! An implementation of the [`CloudFileSigner`] trait for Amazon S3.
2
3use std::time::Duration;
4use std::time::SystemTime;
5
6use aws_config::SdkConfig;
7use aws_sdk_s3::error::SdkError;
8use aws_sdk_s3::operation::get_object::GetObjectError;
9use aws_sdk_s3::presigning::PresigningConfig;
10use aws_sdk_s3::presigning::PresigningConfigError;
11use aws_sdk_s3::Client;
12
13use crate::error::SignerError;
14use crate::permissions::Permission;
15use crate::presigned_url::PresignedUrl;
16use crate::CloudFileSigner;
17
18mod uri;
19
20/// A signer for Amazon S3.
21#[derive(Debug, Clone)]
22pub struct S3FileSigner {
23    client: Client,
24}
25
26impl S3FileSigner {
27    /// Create a new signer for Amazon S3.
28    #[must_use]
29    pub fn new(client: Client) -> Self {
30        Self { client }
31    }
32
33    /// Create a new signer for Amazon S3 from a [`SdkConfig`].
34    pub async fn from_config(config: &SdkConfig) -> Self {
35        let client = Client::new(config);
36        Self { client }
37    }
38
39    /// Create a new signer for Amazon S3 from the environment.
40    pub async fn from_env() -> Self {
41        let config = aws_config::load_from_env().await;
42        let client = Client::new(&config);
43        Self { client }
44    }
45}
46
47impl S3FileSigner {
48    async fn sign_get_request(
49        &self,
50        uri: &uri::S3Uri,
51        expiration: Duration,
52    ) -> Result<PresignedUrl, SignerError> {
53        let valid_from = SystemTime::now();
54
55        let cfg = PresigningConfig::builder().expires_in(expiration).build()?;
56        let presigned_request = self
57            .client
58            .get_object()
59            .bucket(uri.bucket())
60            .key(uri.key())
61            .presigned(cfg)
62            .await?;
63
64        Ok(PresignedUrl::new(
65            presigned_request.uri().to_string(),
66            valid_from,
67            expiration,
68        ))
69    }
70}
71
72#[async_trait::async_trait]
73impl CloudFileSigner for S3FileSigner {
74    async fn sign(
75        &self,
76        path: &str,
77        _valid_from: SystemTime,
78        expiration: Duration,
79        permission: Permission,
80    ) -> Result<PresignedUrl, SignerError> {
81        let s3_uri = path.parse::<uri::S3Uri>()?;
82        match permission {
83            Permission::Read => Ok(self.sign_get_request(&s3_uri, expiration).await?),
84            _ => Err(SignerError::permission_not_supported(format!(
85                "permission {permission:?} not supported"
86            ))),
87        }
88    }
89}
90
91impl From<PresigningConfigError> for SignerError {
92    fn from(e: PresigningConfigError) -> Self {
93        SignerError::other_error(format!("Other error: {e}"))
94    }
95}
96
97impl From<GetObjectError> for SignerError {
98    fn from(e: GetObjectError) -> Self {
99        SignerError::other_error(format!("Other error: {e}"))
100    }
101}
102
103impl<E, R> From<SdkError<E, R>> for SignerError {
104    fn from(e: SdkError<E, R>) -> Self {
105        SignerError::other_error(format!("Other error: {e}"))
106    }
107}