irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

Importer: resolve the token owner's own account via /user so flagged accounts can be imported

huncholanehuncholaneauthored
parent 1bb042dcommit 5f6024c20c43f2ea7b968c4ec67ac79876539a00Browse files

2 files changed, +26 -5

+23-0backend/src/importer/github.rs
@@ -162,6 +162,29 @@ impl GitHub {
162162 .map_err(|e| if e.to_string() == "not found" { anyhow!("GitHub has no user or organization named '{login}'") } else { e })
163163 }
164164
165+ /// The account to import and the token's owner. When the token belongs to
166+ /// the requested login, `/user` answers for it: that also works for
167+ /// accounts GitHub has flagged, whose `/users/{login}` is a 404 to
168+ /// everyone, the owner included.
169+ pub async fn resolve(&self, login: &str) -> anyhow::Result<(RawAccount, Option<RawAccount>)> {
170+ let login = login.trim();
171+ if !valid_login(login) {
172+ return Err(anyhow!("'{login}' is not a valid GitHub username"));
173+ }
174+ let owner = self.token_owner().await?;
175+ if let Some(owner) = owner.as_ref().filter(|o| o.login.eq_ignore_ascii_case(login)) {
176+ return Ok((owner.clone(), Some(owner.clone())));
177+ }
178+ match self.account(login).await {
179+ Ok((account, _)) => Ok((account, owner)),
180+ Err(error) if error.to_string().starts_with("GitHub has no user") => Err(anyhow!(
181+ "GitHub shows no user or organization named '{login}'. If it is your own account and GitHub has \
182+ hidden it, add a token that belongs to it and it can still be imported."
183+ )),
184+ Err(error) => Err(error),
185+ }
186+ }
187+
165188 /// The account the token belongs to, if there is a token.
166189 pub async fn token_owner(&self) -> anyhow::Result<Option<RawAccount>> {
167190 if self.token.is_none() {
+3-5backend/src/importer/mod.rs
@@ -82,8 +82,7 @@ pub fn forget_token(key: &str) {
8282 pub async fn preview(source_owner: &str, token: Option<String>) -> AppResult<shared::GithubPreview> {
8383 let gh = GitHub::new(token)?;
8484 let login = source_owner.trim().trim_start_matches('@').trim_start_matches("https://github.com/").trim_end_matches('/');
85- let (account, _) = gh.account(login).await.map_err(|e| AppError::bad(e.to_string()))?;
86- let token_owner = gh.token_owner().await.map_err(|e| AppError::bad(e.to_string()))?;
85+ let (account, token_owner) = gh.resolve(login).await.map_err(|e| AppError::bad(e.to_string()))?;
8786 let token_is_owner = token_owner.as_ref().is_some_and(|o| o.id == account.id);
8887 let (repos, remaining) = gh.repos(&account, token_is_owner).await.map_err(|e| AppError::bad(e.to_string()))?;
8988 tracing::info!(login = %account.login, repos = repos.len(), token = gh.has_token(), token_is_owner, "github preview");
@@ -126,8 +125,7 @@ pub async fn start(state: &AppState, viewer: &Viewer, request: shared::StartImpo
126125 };
127126
128127 let gh = GitHub::new(request.token.clone())?;
129- let (account, _) = gh.account(&request.source_owner).await.map_err(|e| AppError::bad(e.to_string()))?;
130- let token_owner = gh.token_owner().await.map_err(|e| AppError::bad(e.to_string()))?;
128+ let (account, token_owner) = gh.resolve(&request.source_owner).await.map_err(|e| AppError::bad(e.to_string()))?;
131129 let token_is_owner = token_owner.as_ref().is_some_and(|o| o.id == account.id);
132130 if request.link_emails && !(token_is_owner && target.id == viewer.id) {
133131 return Err(AppError::bad(
@@ -421,7 +419,7 @@ async fn link_emails(state: &AppState, gh: &GitHub, import: &ImportRow) -> anyho
421419 /// Fills empty profile fields (and a missing avatar) from GitHub. Anything
422420 /// already set on irongit is left alone.
423421 async fn import_profile(state: &AppState, gh: &GitHub, import: &ImportRow, owner: &Account) -> anyhow::Result<String> {
424- let (account, _) = gh.account(&import.source_owner).await?;
422+ let (account, _) = gh.resolve(&import.source_owner).await?;
425423 let website = account.blog.clone().unwrap_or_default();
426424 let website = if website.is_empty() || website.starts_with("http") { website } else { format!("https://{website}") };
427425 sqlx::query(