Skip to content

Commit d8fef18

Browse files
evanliu048zixlin7kensavekennvene-aws
authored
feat: Support Qv2 CLI social login (#3060)
* first pass * reuse 2 pkce func * add support for always inclusion steering files (#3047) * redirct index when user cancel auth * add a tmp page when token exchange does not finished * add socail profile * add unified token resolver * only prompt inviation code when need * reuse pkce handle call back logic * telemtry * fmt * ci * add some ut * rename amazonq to kito * add more attempt to connect * comment on ports * add support for always inclusion steering files (#3047) * feat: Adds continuation id logic (#3114) * feat: New continuation id logic * chore: Centralize metadata * chore: Moves struct to right place --------- Co-authored-by: Kenneth S. <[email protected]> * first pass for login with social option using auth portal * recover log change, pkce, telemetry * add which mwinit * delete telemetry and add portal res enum * add auth status to portal * refactor code struc * user friendly msg * allow 3 connectoins * update endpoint for testing * update endpoint for testing * point to prod * clippy --------- Co-authored-by: Zoe Lin <[email protected]> Co-authored-by: Kenneth Sanchez V <[email protected]> Co-authored-by: Kenneth S. <[email protected]>
1 parent de21929 commit d8fef18

File tree

13 files changed

+1010
-124
lines changed

13 files changed

+1010
-124
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/chat-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ zip.workspace = true
122122
rmcp.workspace = true
123123
chat-cli-ui.workspace = true
124124
serde_yaml.workspace = true
125+
urlencoding = "2.1.3"
125126

126127
[target.'cfg(unix)'.dependencies]
127128
nix.workspace = true

crates/chat-cli/src/api_client/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use crate::api_client::model::{
5656
};
5757
use crate::api_client::opt_out::OptOutInterceptor;
5858
use crate::api_client::send_message_output::SendMessageOutput;
59-
use crate::auth::builder_id::BearerResolver;
59+
use crate::auth::UnifiedBearerResolver;
6060
use crate::aws_common::{
6161
UserAgentOverrideInterceptor,
6262
app_name,
@@ -147,7 +147,7 @@ impl ApiClient {
147147
.http_client(crate::aws_common::http_client::client())
148148
.interceptor(OptOutInterceptor::new(database))
149149
.interceptor(UserAgentOverrideInterceptor::new())
150-
.bearer_token_resolver(BearerResolver)
150+
.bearer_token_resolver(UnifiedBearerResolver)
151151
.app_name(app_name())
152152
.endpoint_url(endpoint.url())
153153
.build(),
@@ -208,7 +208,7 @@ impl ApiClient {
208208
.interceptor(OptOutInterceptor::new(database))
209209
.interceptor(UserAgentOverrideInterceptor::new())
210210
.interceptor(DelayTrackingInterceptor::new())
211-
.bearer_token_resolver(BearerResolver)
211+
.bearer_token_resolver(UnifiedBearerResolver)
212212
.app_name(app_name())
213213
.endpoint_resolver(StaticEndpointResolver::new(endpoint.url().to_string()))
214214
.retry_classifier(retry_classifier::QCliRetryClassifier::new())

crates/chat-cli/src/auth/builder_id.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ pub async fn poll_create_token(
543543
}
544544
}
545545

546-
pub async fn is_logged_in(database: &mut Database) -> bool {
546+
pub async fn is_builder_id_logged_in(database: &mut Database) -> bool {
547547
// Check for BuilderId if not using Sigv4
548548
if std::env::var("AMAZON_Q_SIGV4").is_ok_and(|v| !v.is_empty()) {
549549
debug!("logged in using sigv4 credentials");

crates/chat-cli/src/auth/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub(crate) const SCOPES: &[&str] = &[
1818

1919
pub(crate) const CLIENT_TYPE: &str = "public";
2020

21+
pub const SOCIAL_AUTH_SERVICE_ENDPOINT: &str = "https://prod.us-east-1.auth.desktop.kiro.dev";
22+
2123
// The start URL for public builder ID users
2224
pub const START_URL: &str = "https://view.awsapps.com/start";
2325

crates/chat-cli/src/auth/mod.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,31 @@ mod consts;
33
pub mod pkce;
44
mod scope;
55

6+
pub mod portal;
7+
pub mod social;
8+
use aws_sdk_ssooidc::config::{
9+
ConfigBag,
10+
RuntimeComponents,
11+
};
612
use aws_sdk_ssooidc::error::SdkError;
713
use aws_sdk_ssooidc::operation::create_token::CreateTokenError;
814
use aws_sdk_ssooidc::operation::register_client::RegisterClientError;
915
use aws_sdk_ssooidc::operation::start_device_authorization::StartDeviceAuthorizationError;
16+
use aws_smithy_runtime_api::client::identity::http::Token;
17+
use aws_smithy_runtime_api::client::identity::{
18+
Identity,
19+
IdentityFuture,
20+
ResolveIdentity,
21+
};
1022
pub use builder_id::{
11-
is_logged_in,
23+
is_builder_id_logged_in,
1224
logout,
1325
};
1426
pub use consts::START_URL;
1527
use thiserror::Error;
1628

1729
use crate::aws_common::SdkErrorDisplay;
30+
use crate::database::Database;
1831

1932
#[derive(Debug, Error)]
2033
pub enum AuthError {
@@ -48,6 +61,12 @@ pub enum AuthError {
4861
OAuthCustomError(String),
4962
#[error(transparent)]
5063
DatabaseError(#[from] crate::database::DatabaseError),
64+
#[error(transparent)]
65+
Reqwest(#[from] reqwest::Error),
66+
#[error("HTTP error: {0}")]
67+
HttpStatus(reqwest::StatusCode),
68+
#[error("Authentication failed: {0}")]
69+
SocialAuthProviderFailure(String),
5170
}
5271

5372
impl From<aws_sdk_ssooidc::Error> for AuthError {
@@ -73,3 +92,33 @@ impl From<SdkError<StartDeviceAuthorizationError>> for AuthError {
7392
Self::SdkStartDeviceAuthorization(Box::new(value))
7493
}
7594
}
95+
/// Unified bearer token resolver that tries both social and builder ID tokens
96+
#[derive(Debug, Clone)]
97+
pub struct UnifiedBearerResolver;
98+
99+
impl ResolveIdentity for UnifiedBearerResolver {
100+
fn resolve_identity<'a>(
101+
&'a self,
102+
_runtime_components: &'a RuntimeComponents,
103+
_config_bag: &'a ConfigBag,
104+
) -> IdentityFuture<'a> {
105+
IdentityFuture::new_boxed(Box::pin(async {
106+
let database = Database::new().await?;
107+
108+
if let Ok(Some(token)) = builder_id::BuilderIdToken::load(&database).await {
109+
return Ok(Identity::new(
110+
Token::new(token.access_token.0.clone(), Some(token.expires_at.into())),
111+
Some(token.expires_at.into()),
112+
));
113+
}
114+
115+
if let Ok(Some(token)) = social::SocialToken::load(&database).await {
116+
return Ok(Identity::new(
117+
Token::new(token.access_token.0.clone(), Some(token.expires_at.into())),
118+
Some(token.expires_at.into()),
119+
));
120+
}
121+
Err(AuthError::NoToken.into())
122+
}))
123+
}
124+
}

crates/chat-cli/src/auth/pkce.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,14 @@ impl PkceQueryParams {
458458
/// Generates a random 43-octet URL safe string according to the RFC recommendation.
459459
///
460460
/// Reference: https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
461-
fn generate_code_verifier() -> String {
461+
pub fn generate_code_verifier() -> String {
462462
URL_SAFE.encode(rand::random::<[u8; 32]>()).replace('=', "")
463463
}
464464

465465
/// Base64 URL encoded sha256 hash of the code verifier.
466466
///
467467
/// Reference: https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
468-
fn generate_code_challenge(code_verifier: &str) -> String {
468+
pub fn generate_code_challenge(code_verifier: &str) -> String {
469469
use sha2::{
470470
Digest,
471471
Sha256,

0 commit comments

Comments
 (0)