1use std::time::SystemTime;
2
3use anyhow::{anyhow, Result};
4use async_trait::async_trait;
5use time::{Duration, OffsetDateTime};
6
7use crate::config::{get_env_config, get_env_provider, get_file_config, get_file_provider};
8use crate::env::get_env_credentials;
9use crate::error::Error;
10use crate::error::Error::{ConvertSessionTimestampError, Other};
11use crate::io::{find_auth_credentials, save_auth_credentials};
12use crate::sts::{get_auth_credentials, get_client, get_mfa_device_arn};
13
14mod config;
15mod env;
16pub mod error;
17mod io;
18mod sts;
19
20pub struct Credentials {
22 access_key_id: String,
23 secret_access_key: String,
24 session_token: String,
25 session_expiration_timestamp: i64,
26}
27
28impl Credentials {
29 pub fn new(
30 access_key_id: &str,
31 secret_access_key: &str,
32 session_token: &str,
33 session_expiration_timestamp: i64,
34 ) -> Self {
35 Self {
36 access_key_id: String::from(access_key_id),
37 secret_access_key: String::from(secret_access_key),
38 session_token: String::from(session_token),
39 session_expiration_timestamp,
40 }
41 }
42
43 pub fn to_aws_credentials(&self) -> aws_credential_types::Credentials {
44 aws_credential_types::Credentials::new(
45 self.access_key_id(),
46 self.secret_access_key(),
47 Some(String::from(self.session_token())),
48 Some(SystemTime::UNIX_EPOCH + Duration::seconds(self.session_expiration_timestamp())),
49 "aws-mfa",
50 )
51 }
52
53 pub fn access_key_id(&self) -> &str {
54 &self.access_key_id
55 }
56
57 pub fn secret_access_key(&self) -> &str {
58 &self.secret_access_key
59 }
60
61 pub fn session_token(&self) -> &str {
62 &self.session_token
63 }
64
65 pub fn session_expiration_timestamp(&self) -> i64 {
66 self.session_expiration_timestamp
67 }
68
69 pub fn session_duration(&self) -> Result<Duration, Error> {
70 let session_duration =
71 OffsetDateTime::from_unix_timestamp(self.session_expiration_timestamp)
72 .map_err(ConvertSessionTimestampError)?
73 - OffsetDateTime::now_utc();
74
75 Ok(Duration::seconds(session_duration.whole_seconds()))
76 }
77
78 pub fn expired(&self) -> bool {
79 OffsetDateTime::now_utc().unix_timestamp() > self.session_expiration_timestamp
80 }
81}
82
83#[async_trait]
84pub trait CredentialsProvider {
85 async fn validate(&self) -> Result<Option<Credentials>, Error>;
86 async fn authenticate(&self) -> Result<Credentials, Error>;
87}
88
89pub struct FileCredentialsProvider {
91 code: String,
92 home: String,
93 region: Option<String>,
94 profile: String,
95 suffix: String,
96 identifier: Option<String>,
97 duration: i32,
98}
99
100impl FileCredentialsProvider {
101 pub fn new(
102 code: &str,
103 home: &str,
104 region: Option<String>,
105 profile: &str,
106 suffix: &str,
107 identifier: Option<String>,
108 duration: i32,
109 ) -> Self {
110 Self {
111 code: String::from(code),
112 home: String::from(home),
113 region,
114 profile: String::from(profile),
115 suffix: String::from(suffix),
116 identifier,
117 duration,
118 }
119 }
120}
121
122#[async_trait]
123impl CredentialsProvider for FileCredentialsProvider {
124 async fn validate(&self) -> Result<Option<Credentials>, Error> {
126 if let Some(credentials) = find_auth_credentials(&self.home, &self.profile)? {
127 if !credentials.expired() {
128 return Ok(Some(credentials));
129 }
130 }
131
132 Ok(None)
133 }
134
135 async fn authenticate(&self) -> Result<Credentials, Error> {
137 let config =
138 get_file_config(&self.home, self.region.clone(), &self.profile, &self.suffix).await;
139 let provider = get_file_provider(&self.profile, &self.suffix);
140 let client = get_client(&config, provider);
141 let arn = get_mfa_device_arn(&client, self.identifier.clone()).await?;
142 let credentials = get_auth_credentials(&client, &arn, &self.code, self.duration).await?;
143
144 save_auth_credentials(&self.home, &self.profile, &credentials)?;
145
146 Ok(credentials)
147 }
148}
149
150pub struct EnvCredentialsProvider {
152 code: String,
153 identifier: Option<String>,
154 duration: i32,
155}
156
157impl EnvCredentialsProvider {
158 pub fn new(code: &str, identifier: Option<String>, duration: i32) -> Self {
159 Self {
160 code: String::from(code),
161 identifier,
162 duration,
163 }
164 }
165}
166
167#[async_trait]
168impl CredentialsProvider for EnvCredentialsProvider {
169 async fn validate(&self) -> Result<Option<Credentials>, Error> {
171 let provider = get_env_provider();
172 if let Some(credentials) = get_env_credentials(provider).await? {
173 if credentials.expired() {
174 return Err(Other(anyhow!("expired credentials")));
175 }
176
177 return Ok(Some(credentials));
178 }
179
180 Ok(None)
181 }
182
183 async fn authenticate(&self) -> Result<Credentials, Error> {
185 let config = get_env_config().await;
186 let provider = get_env_provider();
187 let client = get_client(&config, provider);
188 let arn = get_mfa_device_arn(&client, self.identifier.clone()).await?;
189 let credentials = get_auth_credentials(&client, &arn, &self.code, self.duration).await?;
190
191 Ok(credentials)
192 }
193}