Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions crates/google-workspace-cli/src/auth_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ fn urlencoding(s: &str) -> String {
percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string()
}

fn ensure_login_identity_scopes(scopes: &mut Vec<String>) {
for s in LOGIN_IDENTITY_SCOPES {
if !scopes.iter().any(|existing| existing == s) {
scopes.push((*s).to_string());
}
}
}

/// Mask a secret string by showing only the first 4 and last 4 characters.
/// Strings with 8 or fewer characters are fully replaced with "***".
///
Expand Down Expand Up @@ -279,6 +287,9 @@ pub const MINIMAL_SCOPES: &[&str] = &[
/// have a verified app or a GCP project with the APIs enabled and approved.
pub const DEFAULT_SCOPES: &[&str] = MINIMAL_SCOPES;

const LOGIN_IDENTITY_SCOPES: &[&str] =
&["openid", "https://www.googleapis.com/auth/userinfo.email"];

/// Full scopes — all common Workspace APIs plus GCP platform access.
///
/// Use `gws auth login --full` to request these. Unverified OAuth apps will
Expand Down Expand Up @@ -598,20 +609,9 @@ async fn handle_login_inner(
// Remove restrictive scopes when broader alternatives are present.
let mut scopes = filter_redundant_restrictive_scopes(scopes);

// Ensure openid + email + profile scopes are always present so we can
// identify the user via the userinfo endpoint after login, and so the
// Gmail helpers can fall back to the People API to populate the From
// display name when the send-as identity lacks one (Workspace accounts).
let identity_scopes = [
"openid",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
];
for s in &identity_scopes {
if !scopes.iter().any(|existing| existing == s) {
scopes.push(s.to_string());
}
}
// Ensure the scopes needed to identify the signed-in account are present.
// Avoid forcing userinfo.profile because some OAuth clients cannot request it.
ensure_login_identity_scopes(&mut scopes);

// Ensure config directory exists
let config = config_dir();
Expand Down Expand Up @@ -2481,6 +2481,21 @@ mod tests {
assert!(result.is_empty());
}

#[test]
fn ensure_login_identity_scopes_does_not_force_profile_scope() {
let mut scopes = vec!["https://www.googleapis.com/auth/gmail.modify".to_string()];

ensure_login_identity_scopes(&mut scopes);

assert!(scopes.iter().any(|scope| scope == "openid"));
assert!(scopes
.iter()
.any(|scope| scope == "https://www.googleapis.com/auth/userinfo.email"));
assert!(!scopes
.iter()
.any(|scope| scope == "https://www.googleapis.com/auth/userinfo.profile"));
}

#[test]
fn build_proxy_auth_url_encodes_scope_and_redirect_uri() {
let scopes = vec![
Expand Down
Loading