check if github token is valid on page load and trigger re-auth flow if not (#120)

This commit is contained in:
Anastasiia Solop
2025-07-10 16:58:02 +02:00
committed by GitHub
parent 35b2631ba6
commit f8af65189f
6 changed files with 118 additions and 13 deletions

View File

@@ -2,7 +2,7 @@ use axum::{
extract::{Request, State},
middleware::Next,
response::{Json as ResponseJson, Response},
routing::post,
routing::{get, post},
Json, Router,
};
@@ -12,6 +12,7 @@ pub fn auth_router() -> Router<AppState> {
Router::new()
.route("/auth/github/device/start", post(device_start))
.route("/auth/github/device/poll", post(device_poll))
.route("/auth/github/check", get(github_check_token))
}
#[derive(serde::Deserialize)]
@@ -263,6 +264,40 @@ async fn device_poll(
}
}
/// GET /auth/github/check
async fn github_check_token(State(app_state): State<AppState>) -> ResponseJson<ApiResponse<()>> {
let config = app_state.get_config().read().await;
let token = config.github.token.clone();
drop(config);
if let Some(token) = token {
let client = reqwest::Client::new();
let res = client
.get("https://api.github.com/user")
.bearer_auth(&token)
.header("User-Agent", "vibe-kanban-app")
.send()
.await;
match res {
Ok(r) if r.status().is_success() => ResponseJson(ApiResponse {
success: true,
data: None,
message: Some("GitHub token is valid".to_string()),
}),
_ => ResponseJson(ApiResponse {
success: false,
data: None,
message: Some("github_token_invalid".to_string()),
}),
}
} else {
ResponseJson(ApiResponse {
success: false,
data: None,
message: Some("github_token_invalid".to_string()),
})
}
}
/// Middleware to set Sentry user context for every request
pub async fn sentry_user_context_middleware(
State(app_state): State<AppState>,

View File

@@ -407,6 +407,9 @@ pub async fn create_github_pr(
e
);
let message = match &e {
crate::models::task_attempt::TaskAttemptError::GitHubService(
crate::services::GitHubServiceError::TokenInvalid,
) => Some("github_token_invalid".to_string()),
crate::models::task_attempt::TaskAttemptError::Git(err)
if err.message().contains("status code: 403") =>
{

View File

@@ -12,6 +12,7 @@ pub enum GitHubServiceError {
Repository(String),
PullRequest(String),
Branch(String),
TokenInvalid,
}
impl std::fmt::Display for GitHubServiceError {
@@ -22,6 +23,7 @@ impl std::fmt::Display for GitHubServiceError {
GitHubServiceError::Repository(e) => write!(f, "Repository error: {}", e),
GitHubServiceError::PullRequest(e) => write!(f, "Pull request error: {}", e),
GitHubServiceError::Branch(e) => write!(f, "Branch error: {}", e),
GitHubServiceError::TokenInvalid => write!(f, "GitHub token is invalid or expired."),
}
}
}
@@ -30,7 +32,22 @@ impl std::error::Error for GitHubServiceError {}
impl From<octocrab::Error> for GitHubServiceError {
fn from(err: octocrab::Error) -> Self {
GitHubServiceError::Client(err)
match &err {
octocrab::Error::GitHub { source, .. } => {
let status = source.status_code.as_u16();
let msg = source.message.to_ascii_lowercase();
if status == 401
|| status == 403
|| msg.contains("bad credentials")
|| msg.contains("token expired")
{
GitHubServiceError::TokenInvalid
} else {
GitHubServiceError::Client(err)
}
}
_ => GitHubServiceError::Client(err),
}
}
}
@@ -161,11 +178,27 @@ impl GitHubService {
.send()
.await
.map_err(|e| match e {
octocrab::Error::GitHub { source, .. } => GitHubServiceError::PullRequest(format!(
"GitHub API error: {} (status: {})",
source.message,
source.status_code.as_u16()
)),
octocrab::Error::GitHub { source, .. } => {
if source.status_code.as_u16() == 401
|| source.status_code.as_u16() == 403
|| source
.message
.to_ascii_lowercase()
.contains("bad credentials")
|| source
.message
.to_ascii_lowercase()
.contains("token expired")
{
GitHubServiceError::TokenInvalid
} else {
GitHubServiceError::PullRequest(format!(
"GitHub API error: {} (status: {})",
source.message,
source.status_code.as_u16()
))
}
}
_ => GitHubServiceError::PullRequest(format!("Failed to create PR: {}", e)),
})?;