Skip to content

feat: remove Bitbucket OAuth, add RawGit username/password auth#1

Open
cloud-hai-vo wants to merge 1 commit into
mainfrom
feat/remove-bitbucket-add-rawgit
Open

feat: remove Bitbucket OAuth, add RawGit username/password auth#1
cloud-hai-vo wants to merge 1 commit into
mainfrom
feat/remove-bitbucket-add-rawgit

Conversation

@cloud-hai-vo
Copy link
Copy Markdown
Contributor

Changes

  • ❌ Remove all Bitbucket OAuth (Device Flow, App Password, identity, keychain key)
  • ✅ Keep GitHub OAuth (Device Flow + PAT) untouched
  • ✅ Add RawGitCredentialService — sync vault to ANY Git remote with username + password
  • Update GitSyncService: GitHub token → raw credential fallback
  • Update SettingsViewModel + SettingsView: Custom Git Remote section
  • Remove Bitbucket tab from OAuthConnectDialog
  • Update tests: remove Bitbucket tests, add RawGitCredentialTests
  • Update README + Blueprint docs

Review checklist

  • No Bitbucket references remain in src/ or tests/
  • GitHub OAuth Device Flow still works
  • RawGitCredentialService stores/retrieves credentials via keychain
  • GitSyncService falls back to raw creds when no GitHub token

/cc @eggspot_devclaw_bot please review 🙏

- Remove BitbucketIdentity, OAuthProvider.Bitbucket, all Bitbucket methods
- Remove BitbucketClientId/Secret from OAuthClientConfig
- Remove KeychainKeys.Bitbucket
- Add RawGitCredentialService (IRawGitCredentialService) with keychain storage
- Update GitSyncService: GitHub token -> raw credential fallback
- Update SettingsViewModel: remove Bitbucket props, add RawGit props/commands
- Update SettingsView.axaml: Custom Git Remote section
- Update OAuthConnectDialog: remove Bitbucket tab
- Remove Bitbucket tests, add RawGitCredentialTests
- Update README + Blueprint
Copilot AI review requested due to automatic review settings April 1, 2026 03:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes all Bitbucket OAuth support and introduces a “Raw Git” username/password credential path intended to allow syncing to arbitrary HTTPS Git remotes, while keeping GitHub OAuth (Device Flow + PAT) intact.

Changes:

  • Removed Bitbucket identity/provider, UI panels, keychain key, and related tests.
  • Added RawGitCredentialService (+ tests) for validating and storing per-remote username/password credentials in the keychain.
  • Updated GitSyncService to resolve credentials via PAT → GitHub token → raw credentials fallback; updated Settings UI/VM to manage custom remote credentials.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/SpotDesk.UI.Tests/M5_IntegrationTests.cs Removes Bitbucket integration test coverage.
tests/SpotDesk.Core.Tests/RawGitCredentialTests.cs Adds unit tests for raw credential storage/normalization/validation.
tests/SpotDesk.Core.Tests/M1_OAuthTests.cs Removes Bitbucket OAuth tests; updates OAuthClientConfig.Resolve call sites in tests.
src/SpotDesk.UI/Views/SettingsView.axaml Replaces Bitbucket Sync UI with Custom Git Remote UI.
src/SpotDesk.UI/Views/MainWindow.axaml.cs Removes Bitbucket identity handling post-auth.
src/SpotDesk.UI/ViewModels/SettingsViewModel.cs Adds raw credential connect/disconnect commands and state.
src/SpotDesk.UI/Dialogs/OAuthConnectDialog.axaml(.cs) Removes Bitbucket panel and wiring; adjusts dialog title.
src/SpotDesk.Core/Vault/VaultService.cs Updates docs to remove Bitbucket references.
src/SpotDesk.Core/Sync/GitSyncService.cs Adds credential resolution logic and raw-credential fallback.
src/SpotDesk.Core/Auth/RawGitCredentialService.cs New service for validating/storing raw HTTPS credentials in keychain.
src/SpotDesk.Core/Auth/OAuthService.cs Removes Bitbucket OAuth/App Password flows and models.
src/SpotDesk.Core/Auth/OAuthClientConfig.cs Removes Bitbucket config fields; simplifies Resolve.
src/SpotDesk.Core/Auth/MasterPasswordFallback.cs Updates docs to remove Bitbucket mention.
src/SpotDesk.Core/Auth/KeychainService.cs Removes Bitbucket key constant.
SpotDesk_Blueprint.md Updates architecture/roadmap docs to remove Bitbucket and add raw git credentials.
README.md Updates marketing/docs to describe GitHub + custom remote sync.
Comments suppressed due to low confidence (1)

README.md:33

  • The Vault Modes table still says Git Sync “First run” is “Sign in via Device Flow”, but the README now also advertises syncing via “any Git remote with username/password”. Update this row to reflect that Git sync can be configured either via GitHub Device Flow/PAT or via custom remote credentials, otherwise the documentation is misleading.
| | Local Mode | Git Sync |
|---|---|---|
| Account required | None | GitHub or any Git remote |
| Encrypted at rest | Yes (AES-256-GCM) | Yes (AES-256-GCM) |
| Sync across devices | No | Yes (private Git repo) |
| Works offline | Yes | Yes (cached locally) |
| First run | Set a master password | Sign in via Device Flow |


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Environment.GetEnvironmentVariable("SPOTDESK_BITBUCKET_CLIENT_SECRET"),
savedBitbucketClientSecret),
};

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OAuthClientConfig.Resolve signature was reduced to a single parameter, but there is still at least one call site using the old 3-argument overload (e.g. src/SpotDesk.App/Program.cs:37). This will fail to compile; update DI bootstrap to call Resolve(null) (and adjust any other remaining call sites).

Suggested change
/// <summary>
/// Backward-compatible overload for older call sites that passed additional, now-unused
/// OAuth client IDs. Extra parameters are ignored and only the GitHub client ID is used.
/// </summary>
public static OAuthClientConfig Resolve(
string? savedGitHubClientId,
string? savedAzureClientId,
string? savedGoogleClientId) =>
Resolve(savedGitHubClientId);

Copilot uses AI. Check for mistakes.
private async Task ConnectRawGitAsync()
{
BitbucketStatus = "Validating…";
if (_rawGitCredential is null) return;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConnectRawGitAsync silently no-ops when _rawGitCredential is null. In the current DI setup there is no registration for IRawGitCredentialService (and SettingsViewModel is constructed without it), so the Custom Git Remote UI will never work. Consider making _rawGitCredential non-nullable and registering it in DI (and/or set RawGitStatus to an explicit error when it’s missing).

Suggested change
if (_rawGitCredential is null) return;
if (_rawGitCredential is null)
{
RawGitStatus = "Error: Raw Git credential service is not available.";
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +56
// Try GitHub token-based auth first
var token = _keychain.Retrieve(KeychainKeys.VaultRepoPat)
?? _keychain.Retrieve(KeychainKeys.GitHub);

if (!string.IsNullOrEmpty(token))
return new UsernamePasswordCredentials { Username = "x-token", Password = token };

// Fall back to raw git credentials for this remote URL
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolveCredentials always prefers GitHub/PAT tokens even when syncing against a non-GitHub remote. If a user is signed into GitHub (needed for vault unlock) but configures a custom GitLab/Gitea remote, Git operations will attempt to authenticate with the GitHub token and fail, never reaching the raw-credential fallback. Consider selecting the auth mechanism based on the remote URL host/scheme (e.g., only use GitHub token for GitHub remotes, otherwise prefer raw credentials for that URL).

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +67
if (string.IsNullOrWhiteSpace(remoteUrl))
throw new ArgumentException("Remote URL must not be empty.", nameof(remoteUrl));
if (string.IsNullOrWhiteSpace(username))
throw new ArgumentException("Username must not be empty.", nameof(username));
if (string.IsNullOrWhiteSpace(password))
throw new ArgumentException("Password must not be empty.", nameof(password));

await Task.Run(() =>
{
try
{
Repository.ListRemoteReferences(remoteUrl.Trim(), (_, _, _) =>
new UsernamePasswordCredentials
{
Username = username.Trim(),
Password = password.Trim()
});
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateAsync accepts any remoteUrl string and then uses UsernamePasswordCredentials, which only applies to HTTP(S) remotes. To avoid confusing failures and credential leaks, validate that the URL is a well-formed HTTPS URL and reject URLs containing embedded credentials (userinfo like https://user:pass@host/...) before attempting authentication.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +38
public void Store(string remoteUrl, string username, string password)
{
var key = NormalizeKey(remoteUrl);
_keychain.Store(key, $"{username}\n{password}");
}

public (string Username, string Password)? Retrieve(string remoteUrl)
{
var key = NormalizeKey(remoteUrl);
var stored = _keychain.Retrieve(key);
if (stored is null) return null;

var newlineIdx = stored.IndexOf('\n');
if (newlineIdx < 0) return null;

return (stored[..newlineIdx], stored[(newlineIdx + 1)..]);
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw credentials are stored as a single string using a newline delimiter ($"{username}\n{password}"). Because username is not constrained, a newline in the username would make retrieval ambiguous/corrupted. Add validation to forbid \r/\n in the username (and consider a less fragile encoding/serialization format).

Copilot uses AI. Check for mistakes.
RawGitRemoteUrl = string.Empty;
RawGitUsername = string.Empty;
RawGitPassword = string.Empty;
RawGitStatus = string.Empty;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DisconnectRawGit relies on _rawGitCredential.Delete(...), but the current MacOsKeychainService.Delete implementation is a no-op (src/SpotDesk.Core/Auth/KeychainService.cs:125). On macOS this means “Disconnect” won’t actually remove stored raw credentials (and likewise GitHub revoke). Consider implementing keychain deletion on macOS or changing the UI/wording to avoid implying credentials were removed when they weren’t.

Suggested change
RawGitStatus = string.Empty;
RawGitStatus = "Disconnected from raw Git. OS-level Git credentials (e.g. in Keychain or the system credential manager) may need to be removed separately.";

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +62
/// Returns the best available credentials for git operations.
/// Priority: vault-repo fine-grained PAT → GitHub identity token → raw git credentials for the remote URL.
/// </summary>
private string ResolveGitToken() =>
_keychain.Retrieve(KeychainKeys.VaultRepoPat)
?? _keychain.Retrieve(KeychainKeys.GitHub)
?? string.Empty;
private Credentials ResolveCredentials(string url)
{
// Try GitHub token-based auth first
var token = _keychain.Retrieve(KeychainKeys.VaultRepoPat)
?? _keychain.Retrieve(KeychainKeys.GitHub);

if (!string.IsNullOrEmpty(token))
return new UsernamePasswordCredentials { Username = "x-token", Password = token };

// Fall back to raw git credentials for this remote URL
if (_rawGit is not null)
{
var rawCred = _rawGit.Retrieve(url);
if (rawCred is not null)
return new UsernamePasswordCredentials { Username = rawCred.Value.Username, Password = rawCred.Value.Password };
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolveCredentials introduces new credential-selection behavior (PAT vs GitHub token vs raw credentials), but there are no unit tests asserting which credential path is chosen under each condition. Since tests/SpotDesk.Core.Tests/M2_GitSyncTests.cs already covers GitSyncService, consider adding tests that verify (1) PAT takes precedence over GitHub token, (2) raw credentials are used when tokens are absent, and (3) behavior for non-GitHub remotes when a GitHub token exists.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants