Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixed repeated auths prompt (close #652) #799

Merged
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ export interface IAuth {
password: string;
}

export interface IStoredAuth {
account: string;
password: string;
}
export interface ISvnLogEntryPath {
/** full path from repo root */
_: string;
Expand Down
61 changes: 58 additions & 3 deletions src/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
Status,
SvnDepth,
SvnUriAction,
ISvnPathChange
ISvnPathChange,
IStoredAuth
} from "./common/types";
import { debounce, globalSequentialize, memoize, throttle } from "./decorators";
import { exists } from "./fs";
Expand All @@ -50,6 +51,7 @@ import {
} from "./util";
import { match, matchAll } from "./util/globMatch";
import { RepositoryFilesWatcher } from "./watchers/repositoryFilesWatcher";
import { keytar } from "./vscodeModules";

function shouldShowProgress(operation: Operation): boolean {
switch (operation) {
Expand Down Expand Up @@ -79,6 +81,7 @@ export class Repository implements IRemoteRepository {
public needCleanUp: boolean = false;
private remoteChangedUpdateInterval?: NodeJS.Timer;
private deletedUris: Uri[] = [];
private canSaveAuth: boolean = false;

private lastPromptAuth?: Thenable<IAuth | undefined>;

Expand Down Expand Up @@ -919,6 +922,39 @@ export class Repository implements IRemoteRepository {
return new PathNormalizer(this.repository.info);
}

protected getCredentialServiceName() {
let key = "vscode.svn-scm";

const info = this.repository.info;

if (info.repository && info.repository.root) {
key += ":" + info.repository.root;
} else if (info.url) {
key += ":" + info.url;
}

return key;
}

public async loadStoredAuths(): Promise<Array<IStoredAuth>> {
// Prevent multiple prompts for auth
if (this.lastPromptAuth) {
await this.lastPromptAuth;
}
return keytar.findCredentials(this.getCredentialServiceName());
}

public async saveAuth(): Promise<void> {
if (this.canSaveAuth && this.username && this.password) {
await keytar.setPassword(
this.getCredentialServiceName(),
this.username,
this.password
);
this.canSaveAuth = false;
}
}

public async promptAuth(): Promise<IAuth | undefined> {
// Prevent multiple prompts for auth
if (this.lastPromptAuth) {
Expand All @@ -931,6 +967,7 @@ export class Repository implements IRemoteRepository {
if (result) {
this.username = result.username;
this.password = result.password;
this.canSaveAuth = true;
}

this.lastPromptAuth = undefined;
Expand Down Expand Up @@ -1002,11 +1039,14 @@ export class Repository implements IRemoteRepository {
runOperation: () => Promise<T> = () => Promise.resolve<any>(null)
): Promise<T> {
let attempt = 0;
let accounts: IStoredAuth[] = [];

while (true) {
try {
attempt++;
return await runOperation();
const result = await runOperation();
this.saveAuth();
return result;
} catch (err) {
if (
err.svnErrorCode === svnErrorCodes.RepositoryIsLocked &&
Expand All @@ -1016,7 +1056,22 @@ export class Repository implements IRemoteRepository {
await timeout(Math.pow(attempt, 2) * 50);
} else if (
err.svnErrorCode === svnErrorCodes.AuthorizationFailed &&
attempt <= 3
attempt <= 1 + accounts.length
) {
// First attempt load all stored auths
if (attempt === 1) {
accounts = await this.loadStoredAuths();
}

// each attempt, try a different account
const index = accounts.length - 1;
if (typeof accounts[index] !== "undefined") {
this.username = accounts[index].account;
this.password = accounts[index].password;
}
} else if (
err.svnErrorCode === svnErrorCodes.AuthorizationFailed &&
attempt <= 3 + accounts.length
) {
const result = await this.promptAuth();
if (!result) {
Expand Down
65 changes: 65 additions & 0 deletions src/types/keytar.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Definitions by: Milan Burda <https://github.com/miniak>, Brendan Forster <https://github.com/shiftkey>, Hari Juturu <https://github.com/juturu>
// Adapted from DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/keytar/index.d.ts

declare module "keytar" {
/**
* Get the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the password string.
*/
export declare function getPassword(
service: string,
account: string
): Promise<string | null>;

/**
* Add the password for the service and account to the keychain.
*
* @param service The string service name.
* @param account The string account name.
* @param password The string password.
*
* @returns A promise for the set password completion.
*/
export declare function setPassword(
service: string,
account: string,
password: string
): Promise<void>;

/**
* Delete the stored password for the service and account.
*
* @param service The string service name.
* @param account The string account name.
*
* @returns A promise for the deletion status. True on success.
*/
export declare function deletePassword(
service: string,
account: string
): Promise<boolean>;

/**
* Find a password for the service in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the password string.
*/
export declare function findPassword(service: string): Promise<string | null>;

/**
* Find all accounts and passwords for `service` in the keychain.
*
* @param service The string service name.
*
* @returns A promise for the array of found credentials.
*/
export declare function findCredentials(
service: string
): Promise<Array<{ account: string; password: string }>>;
}
1 change: 1 addition & 0 deletions src/vscodeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export const iconv = loadVSCodeModule(
export const jschardet = loadVSCodeModule(
"jschardet"
) as typeof import("jschardet");
export const keytar = loadVSCodeModule("keytar") as typeof import("keytar");