重构git凭证托管,改为操作系统托管
This commit is contained in:
46
src-tauri/Cargo.lock
generated
46
src-tauri/Cargo.lock
generated
@@ -802,6 +802,7 @@ dependencies = [
|
|||||||
"fix-path-env",
|
"fix-path-env",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"keyring",
|
||||||
"kill_tree",
|
"kill_tree",
|
||||||
"notify",
|
"notify",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
@@ -1151,6 +1152,27 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus"
|
||||||
|
version = "0.9.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus-secret-service"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||||
|
dependencies = [
|
||||||
|
"dbus",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deflate64"
|
name = "deflate64"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
@@ -2716,6 +2738,21 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyring"
|
||||||
|
version = "3.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"dbus-secret-service",
|
||||||
|
"log",
|
||||||
|
"security-framework 2.11.1",
|
||||||
|
"security-framework 3.5.1",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kill_tree"
|
name = "kill_tree"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -2806,6 +2843,15 @@ version = "0.2.180"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ base64 = "0.22"
|
|||||||
agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] }
|
agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] }
|
||||||
kill_tree = { version = "0.2", features = ["tokio"] }
|
kill_tree = { version = "0.2", features = ["tokio"] }
|
||||||
which = "7"
|
which = "7"
|
||||||
|
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-window-state = "2"
|
tauri-plugin-window-state = "2"
|
||||||
|
|||||||
@@ -203,6 +203,38 @@ pub async fn update_github_accounts(
|
|||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Keyring token management
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_account_token(
|
||||||
|
account_id: String,
|
||||||
|
token: String,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
crate::keyring_store::set_token(&account_id, &token)
|
||||||
|
.map_err(|e| AppCommandError::io_error("Failed to save token to keyring").with_detail(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_account_token(
|
||||||
|
account_id: String,
|
||||||
|
) -> Result<Option<String>, AppCommandError> {
|
||||||
|
Ok(crate::keyring_store::get_token(&account_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_account_token(
|
||||||
|
account_id: String,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
crate::keyring_store::delete_token(&account_id)
|
||||||
|
.map_err(|e| AppCommandError::io_error("Failed to delete token from keyring").with_detail(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// GitHub token validation
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct GitHubUserResponse {
|
struct GitHubUserResponse {
|
||||||
login: String,
|
login: String,
|
||||||
|
|||||||
@@ -123,8 +123,10 @@ pub fn run_credential_helper() {
|
|||||||
|
|
||||||
let remote_url = format!("https://{}", host);
|
let remote_url = format!("https://{}", host);
|
||||||
if let Some(account) = find_matching_account(&settings.accounts, &remote_url) {
|
if let Some(account) = find_matching_account(&settings.accounts, &remote_url) {
|
||||||
println!("username={}", account.username);
|
if let Some(token) = crate::keyring_store::get_token(&account.id) {
|
||||||
println!("password={}", account.token);
|
println!("username={}", account.username);
|
||||||
|
println!("password={}", token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -387,11 +389,19 @@ pub async fn try_inject_for_repo(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let token = match crate::keyring_store::get_token(&account.id) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
eprintln!("[GIT_CRED] no token in keyring for account {}", account.id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[GIT_CRED] injecting credentials for {} (user: {})",
|
"[GIT_CRED] injecting credentials for {} (user: {})",
|
||||||
remote_url, account.username
|
remote_url, account.username
|
||||||
);
|
);
|
||||||
inject_credentials(cmd, &account.username, &account.token, &askpass);
|
inject_credentials(cmd, &account.username, &token, &askpass);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,6 +427,14 @@ pub async fn try_inject_for_url(
|
|||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let token = match crate::keyring_store::get_token(&account.id) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
eprintln!("[GIT_CRED] no token in keyring for account {}", account.id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let askpass = match ensure_askpass_script(app_data_dir) {
|
let askpass = match ensure_askpass_script(app_data_dir) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -425,7 +443,7 @@ pub async fn try_inject_for_url(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inject_credentials(cmd, &account.username, &account.token, &askpass);
|
inject_credentials(cmd, &account.username, &token, &askpass);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +482,6 @@ mod tests {
|
|||||||
id: "1".into(),
|
id: "1".into(),
|
||||||
server_url: "https://github.com".into(),
|
server_url: "https://github.com".into(),
|
||||||
username: "user1".into(),
|
username: "user1".into(),
|
||||||
token: "tok1".into(),
|
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
@@ -474,7 +491,6 @@ mod tests {
|
|||||||
id: "2".into(),
|
id: "2".into(),
|
||||||
server_url: "https://gitlab.example.com".into(),
|
server_url: "https://gitlab.example.com".into(),
|
||||||
username: "user2".into(),
|
username: "user2".into(),
|
||||||
token: "tok2".into(),
|
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
is_default: true,
|
is_default: true,
|
||||||
@@ -500,7 +516,6 @@ mod tests {
|
|||||||
id: "1".into(),
|
id: "1".into(),
|
||||||
server_url: "https://github.com".into(),
|
server_url: "https://github.com".into(),
|
||||||
username: "personal".into(),
|
username: "personal".into(),
|
||||||
token: "tok1".into(),
|
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
@@ -510,7 +525,6 @@ mod tests {
|
|||||||
id: "2".into(),
|
id: "2".into(),
|
||||||
server_url: "https://github.com".into(),
|
server_url: "https://github.com".into(),
|
||||||
username: "work".into(),
|
username: "work".into(),
|
||||||
token: "tok2".into(),
|
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
is_default: true,
|
is_default: true,
|
||||||
|
|||||||
30
src-tauri/src/keyring_store.rs
Normal file
30
src-tauri/src/keyring_store.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use keyring::Entry;
|
||||||
|
|
||||||
|
const SERVICE_NAME: &str = "codeg";
|
||||||
|
|
||||||
|
fn token_key(account_id: &str) -> String {
|
||||||
|
format!("github-token:{}", account_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_token(account_id: &str, token: &str) -> Result<(), String> {
|
||||||
|
let entry = Entry::new(SERVICE_NAME, &token_key(account_id))
|
||||||
|
.map_err(|e| format!("keyring init error: {e}"))?;
|
||||||
|
entry
|
||||||
|
.set_password(token)
|
||||||
|
.map_err(|e| format!("keyring set error: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_token(account_id: &str) -> Option<String> {
|
||||||
|
let entry = Entry::new(SERVICE_NAME, &token_key(account_id)).ok()?;
|
||||||
|
entry.get_password().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_token(account_id: &str) -> Result<(), String> {
|
||||||
|
let entry = Entry::new(SERVICE_NAME, &token_key(account_id))
|
||||||
|
.map_err(|e| format!("keyring init error: {e}"))?;
|
||||||
|
match entry.delete_credential() {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(keyring::Error::NoEntry) => Ok(()),
|
||||||
|
Err(e) => Err(format!("keyring delete error: {e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ mod app_error;
|
|||||||
mod commands;
|
mod commands;
|
||||||
mod db;
|
mod db;
|
||||||
pub mod git_credential;
|
pub mod git_credential;
|
||||||
|
pub mod keyring_store;
|
||||||
mod models;
|
mod models;
|
||||||
mod network;
|
mod network;
|
||||||
mod parsers;
|
mod parsers;
|
||||||
@@ -272,6 +273,9 @@ pub fn run() {
|
|||||||
version_control::get_github_accounts,
|
version_control::get_github_accounts,
|
||||||
version_control::validate_github_token,
|
version_control::validate_github_token,
|
||||||
version_control::update_github_accounts,
|
version_control::update_github_accounts,
|
||||||
|
version_control::save_account_token,
|
||||||
|
version_control::get_account_token,
|
||||||
|
version_control::delete_account_token,
|
||||||
acp_commands::acp_preflight,
|
acp_commands::acp_preflight,
|
||||||
acp_commands::acp_connect,
|
acp_commands::acp_connect,
|
||||||
acp_commands::acp_prompt,
|
acp_commands::acp_prompt,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ pub struct GitHubAccount {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub server_url: String,
|
pub server_url: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub token: String,
|
|
||||||
pub scopes: Vec<String>,
|
pub scopes: Vec<String>,
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
pub is_default: bool,
|
pub is_default: bool,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
|
import { saveAccountToken } from "@/lib/tauri"
|
||||||
import type { GitHubAccount } from "@/lib/types"
|
import type { GitHubAccount } from "@/lib/types"
|
||||||
|
|
||||||
interface AddGitAccountDialogProps {
|
interface AddGitAccountDialogProps {
|
||||||
@@ -52,7 +53,7 @@ export function AddGitAccountDialog({
|
|||||||
[onOpenChange, resetForm]
|
[onOpenChange, resetForm]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(async () => {
|
||||||
const trimmedUrl = serverUrl.trim()
|
const trimmedUrl = serverUrl.trim()
|
||||||
const trimmedUser = username.trim()
|
const trimmedUser = username.trim()
|
||||||
const trimmedPass = password.trim()
|
const trimmedPass = password.trim()
|
||||||
@@ -74,13 +75,19 @@ export function AddGitAccountDialog({
|
|||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
server_url: trimmedUrl,
|
server_url: trimmedUrl,
|
||||||
username: trimmedUser,
|
username: trimmedUser,
|
||||||
token: trimmedPass,
|
|
||||||
scopes: [],
|
scopes: [],
|
||||||
avatar_url: null,
|
avatar_url: null,
|
||||||
is_default: isFirstAccount,
|
is_default: isFirstAccount,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await saveAccountToken(account.id, trimmedPass)
|
||||||
|
} catch {
|
||||||
|
setError(t("gitAccount.passwordRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
onAccountAdded(account)
|
onAccountAdded(account)
|
||||||
handleOpenChange(false)
|
handleOpenChange(false)
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { validateGitHubToken } from "@/lib/tauri"
|
import { validateGitHubToken, saveAccountToken } from "@/lib/tauri"
|
||||||
import type { GitHubAccount } from "@/lib/types"
|
import type { GitHubAccount } from "@/lib/types"
|
||||||
|
|
||||||
interface AddGitHubAccountDialogProps {
|
interface AddGitHubAccountDialogProps {
|
||||||
@@ -94,13 +94,13 @@ export function AddGitHubAccountDialog({
|
|||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
server_url: serverUrl.trim() || "https://github.com",
|
server_url: serverUrl.trim() || "https://github.com",
|
||||||
username: result.username ?? "unknown",
|
username: result.username ?? "unknown",
|
||||||
token: trimmedToken,
|
|
||||||
scopes: result.scopes,
|
scopes: result.scopes,
|
||||||
avatar_url: result.avatar_url,
|
avatar_url: result.avatar_url,
|
||||||
is_default: isFirstAccount,
|
is_default: isFirstAccount,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveAccountToken(account.id, trimmedToken)
|
||||||
onAccountAdded(account)
|
onAccountAdded(account)
|
||||||
handleOpenChange(false)
|
handleOpenChange(false)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import {
|
|||||||
getGitHubAccounts,
|
getGitHubAccounts,
|
||||||
updateGitHubAccounts,
|
updateGitHubAccounts,
|
||||||
validateGitHubToken,
|
validateGitHubToken,
|
||||||
|
getAccountToken,
|
||||||
|
deleteAccountToken,
|
||||||
} from "@/lib/tauri"
|
} from "@/lib/tauri"
|
||||||
import type {
|
import type {
|
||||||
GitDetectResult,
|
GitDetectResult,
|
||||||
@@ -260,10 +262,15 @@ export function VersionControlSettings() {
|
|||||||
async (account: GitHubAccount) => {
|
async (account: GitHubAccount) => {
|
||||||
setTestingAccountId(account.id)
|
setTestingAccountId(account.id)
|
||||||
try {
|
try {
|
||||||
|
const token = await getAccountToken(account.id)
|
||||||
|
if (!token) {
|
||||||
|
toast.error(t("connectionFailed", { message: "Token not found" }))
|
||||||
|
return
|
||||||
|
}
|
||||||
if (isGitHubAccount(account)) {
|
if (isGitHubAccount(account)) {
|
||||||
const result = await validateGitHubToken(
|
const result = await validateGitHubToken(
|
||||||
account.server_url,
|
account.server_url,
|
||||||
account.token
|
token
|
||||||
)
|
)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success(t("connectionSuccess"))
|
toast.success(t("connectionSuccess"))
|
||||||
@@ -276,7 +283,7 @@ export function VersionControlSettings() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For non-GitHub accounts we can't validate via API,
|
// For non-GitHub accounts we can't validate via API,
|
||||||
// just confirm the account is stored.
|
// just confirm the token exists in keyring.
|
||||||
toast.success(t("connectionSuccess"))
|
toast.success(t("connectionSuccess"))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -315,6 +322,7 @@ export function VersionControlSettings() {
|
|||||||
accounts: accounts.accounts.filter((a) => a.id !== removeTarget.id),
|
accounts: accounts.accounts.filter((a) => a.id !== removeTarget.id),
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
await deleteAccountToken(removeTarget.id)
|
||||||
const saved = await updateGitHubAccounts(updated)
|
const saved = await updateGitHubAccounts(updated)
|
||||||
setAccounts(saved)
|
setAccounts(saved)
|
||||||
toast.success(t("removeSuccess"))
|
toast.success(t("removeSuccess"))
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
validateGitHubToken,
|
validateGitHubToken,
|
||||||
getGitHubAccounts,
|
getGitHubAccounts,
|
||||||
updateGitHubAccounts,
|
updateGitHubAccounts,
|
||||||
|
saveAccountToken,
|
||||||
} from "@/lib/tauri"
|
} from "@/lib/tauri"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -146,14 +147,15 @@ async function saveGenericAccount(
|
|||||||
(a) => a.username === creds.username && extractHost(a.server_url) === host
|
(a) => a.username === creds.username && extractHost(a.server_url) === host
|
||||||
)
|
)
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
|
const newId = crypto.randomUUID()
|
||||||
|
await saveAccountToken(newId, creds.password)
|
||||||
await updateGitHubAccounts({
|
await updateGitHubAccounts({
|
||||||
accounts: [
|
accounts: [
|
||||||
...existing.accounts,
|
...existing.accounts,
|
||||||
{
|
{
|
||||||
id: crypto.randomUUID(),
|
id: newId,
|
||||||
server_url: serverUrl,
|
server_url: serverUrl,
|
||||||
username: creds.username,
|
username: creds.username,
|
||||||
token: creds.password,
|
|
||||||
scopes: [],
|
scopes: [],
|
||||||
avatar_url: null,
|
avatar_url: null,
|
||||||
is_default: existing.accounts.length === 0,
|
is_default: existing.accounts.length === 0,
|
||||||
@@ -284,12 +286,12 @@ export function GitCredentialProvider({ children }: { children: ReactNode }) {
|
|||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
server_url: serverUrl,
|
server_url: serverUrl,
|
||||||
username: result.username ?? "unknown",
|
username: result.username ?? "unknown",
|
||||||
token: trimmedToken,
|
|
||||||
scopes: result.scopes,
|
scopes: result.scopes,
|
||||||
avatar_url: result.avatar_url,
|
avatar_url: result.avatar_url,
|
||||||
is_default: existing.accounts.length === 0,
|
is_default: existing.accounts.length === 0,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
await saveAccountToken(newAccount.id, trimmedToken)
|
||||||
await updateGitHubAccounts({
|
await updateGitHubAccounts({
|
||||||
accounts: [...existing.accounts, newAccount],
|
accounts: [...existing.accounts, newAccount],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -344,6 +344,25 @@ export async function updateGitHubAccounts(
|
|||||||
return invoke("update_github_accounts", { settings })
|
return invoke("update_github_accounts", { settings })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveAccountToken(
|
||||||
|
accountId: string,
|
||||||
|
token: string
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke("save_account_token", { accountId, token })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccountToken(
|
||||||
|
accountId: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
return invoke("get_account_token", { accountId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAccountToken(
|
||||||
|
accountId: string
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke("delete_account_token", { accountId })
|
||||||
|
}
|
||||||
|
|
||||||
export async function mcpScanLocal(): Promise<LocalMcpServer[]> {
|
export async function mcpScanLocal(): Promise<LocalMcpServer[]> {
|
||||||
return invoke("mcp_scan_local")
|
return invoke("mcp_scan_local")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -544,7 +544,6 @@ export interface GitHubAccount {
|
|||||||
id: string
|
id: string
|
||||||
server_url: string
|
server_url: string
|
||||||
username: string
|
username: string
|
||||||
token: string
|
|
||||||
scopes: string[]
|
scopes: string[]
|
||||||
avatar_url: string | null
|
avatar_url: string | null
|
||||||
is_default: boolean
|
is_default: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user