Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
Implemented secure stores (#84)
Browse files Browse the repository at this point in the history
* Implemented keystore
  • Loading branch information
Anna Kocheshkova committed May 11, 2018
1 parent 8d178b6 commit 523519c
Show file tree
Hide file tree
Showing 11 changed files with 664 additions and 21 deletions.
Binary file added bin/windows/creds.exe
Binary file not shown.
10 changes: 2 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@
"chalk": "^2.4.1",
"date-fns": "^1.29.0",
"del": "^3.0.0",
"event-stream": "^3.3.4",
"git-url-parse": "^8.3.1",
"gradle-to-js": "^1.1.0",
"gulp-mocha": "^5.0.0",
Expand Down
46 changes: 34 additions & 12 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { tokenStore } from "../data/tokenStore";
import { fileTokenStore } from "../data/tokenStore";
import { ILogger } from "../extension/log/logHelper";
import { LoginInfo, Profile, ProfileStorage } from "../helpers/interfaces";
import { LogStrings } from "../extension/resources/logStrings";
Expand Down Expand Up @@ -50,7 +51,11 @@ export default abstract class Auth<T extends Profile> {

// Remove existent token for user from local store
// TODO: Probably we need to delete token from server also?
await tokenStore.remove(profile.userId);
try {
await tokenStore.remove(profile.userId);
} catch (e) {
// It is ok if removing token failed.
}

await tokenStore.set(profile.userId, { token: token });

Expand All @@ -64,9 +69,14 @@ export default abstract class Auth<T extends Profile> {

public async doLogout(userId: string): Promise<void> {

// Remove token from local store
// Remove token from the store
// TODO: Probably we need to delete token from server also?
await tokenStore.remove(userId);
try {
await tokenStore.remove(userId);
} catch (e) {
// If removing token fails, then maybe we have something stored in the file storage, need to clean that up, too.
await fileTokenStore.remove(userId);
}
await this.profileStorage.delete(userId);

// If there are no profiles left just exit
Expand All @@ -91,19 +101,31 @@ export default abstract class Auth<T extends Profile> {
return await this.profileStorage.list();
}

public static accessTokenFor(profile: Profile): Promise<string> {
const getter = tokenStore.get(profile.userId);
public static async accessTokenFor(profile: Profile): Promise<string> {
const emptyToken = "";
// tslint:disable-next-line:no-any
return getter.then((entry: any) => {
try {
const entry = await tokenStore.get(profile.userId);
if (entry) {
return entry.accessToken.token;
}
return emptyToken;
}).catch((e: Error) => {
// TODO Find a way to log it via logger
console.error(LogStrings.FailedToGetToken, e);
return emptyToken;
});
throw new Error("Empty token!");
} catch (err) {
// compatibility
try {
const oldToken = await fileTokenStore.get(profile.userId);
if (oldToken) {
await fileTokenStore.remove(profile.userId);
await tokenStore.set(profile.userId, { token: oldToken.accessToken.token });
return oldToken.accessToken.token;
}
return emptyToken;
} catch (e) {
// TODO Find a way to log it via logger
console.error(LogStrings.FailedToGetToken, err);
return emptyToken;
}

}
}
}
12 changes: 11 additions & 1 deletion src/data/tokenStore/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as os from "os";
import * as fs from "fs";
import * as path from "path";
import { Constants } from "../../extension/resources/constants";
import { Utils } from "../../helpers/utils/utils";
import { createFileTokenStore } from "./fileTokenStore";
import { TokenStore } from "./tokenStore";
import { createWinTokenStore } from "./win32/win-token-store";
import { createOsxTokenStore } from "./osx/osx-token-store";

export * from "./tokenStore";

Expand All @@ -25,5 +28,12 @@ const getTokenFilePath = (tokenFile: string) => {
return tokenFilePath;
};

store = createFileTokenStore(getTokenFilePath(Constants.AppCenterTokenFileName));
if (os.platform() === "win32") {
store = createWinTokenStore();
} else if (os.platform() === "darwin") {
store = createOsxTokenStore();
} else {
store = createFileTokenStore(getTokenFilePath(Constants.AppCenterTokenFileName));
}
export const tokenStore = store;
export const fileTokenStore = createFileTokenStore(getTokenFilePath(Constants.AppCenterTokenFileName));
102 changes: 102 additions & 0 deletions src/data/tokenStore/osx/osx-keychain-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// Parser for the output of the security(1) command line.
//

import * as es from "event-stream";
import * as stream from "stream";

//
// Regular expressions that match the various fields in the input
//

// Fields at the root - not attributes
const rootFieldRe = /^([^: \t]+):(?: (?:"([^"]+)")|(.*))?$/;

// Attribute values, this gets a little more complicated
// tslint:disable-next-line:no-regex-spaces
const attrRe = /^ (?:(0x[0-9a-fA-F]+) |"([a-z]{4})")<[^>]+>=(?:(<NULL>)|"([^"]+)"|(0x[0-9a-fA-F]+)(?: "([^"]+)")|(.*)?)/;

//
// Stream based parser for the OSX security(1) program output.
// Implements a simple state machine. States are:
//
// 0 - Waiting for the initial "keychain" string.
// 1 - Waiting for the "attributes" string. adds any properties to the
// current entry object being parsed while waiting.
// 2 - reading attributes. Continues adding the attributes to the
// current entry object until we hit either a non-indented line
// or end. At which point we emit.
//

export class OsxSecurityParsingStream extends stream.Transform {
private currentEntry: any;
private inAttributes: boolean;

constructor() {
super({ objectMode: true });
this.currentEntry = null;
this.inAttributes = false;
}

public _transform(chunk: any, _encoding: string, callback: { (err?: Error): void }): void {
const line = chunk.toString();

// debug(`Parsing line [${line}]`);

const rootMatch = line.match(rootFieldRe);
if (rootMatch) {
this.processRootLine(rootMatch[1], rootMatch[2] || rootMatch[3]);
} else {
const attrMatch = line.match(attrRe);
if (attrMatch) {
// Did we match a four-char named field? We don't care about hex fields
if (attrMatch[2]) {
// We skip nulls, and grab text rather than hex encoded versions of value
const value = attrMatch[6] || attrMatch[4];
if (value) {
this.processAttributeLine(attrMatch[2], value);
}
}
}
}
callback();
}

public _flush(callback: { (err?: Error): void }): void {
this.emitCurrentEntry();
callback();
}

public emitCurrentEntry(): void {
if (this.currentEntry) {
this.push(this.currentEntry);
this.currentEntry = null;
}
}

public processRootLine(key: string, value: string): void {
//debug(`matched root line`);
if (this.inAttributes) {
// debug(`was in attributes, emitting`);
this.emitCurrentEntry();
this.inAttributes = false;
}
if (key === "attributes") {
// debug(`now in attributes`);
this.inAttributes = true;
} else {
// debug(`adding root attribute ${key} with value ${value} to object`);
this.currentEntry = this.currentEntry || {};
this.currentEntry[key] = value;
}
}

public processAttributeLine(key: string, value: string): void {
//debug(`adding attribute ${key} with value ${value} to object`);
this.currentEntry[key] = value;
}
}

export function createOsxSecurityParsingStream(): NodeJS.ReadWriteStream {
return es.pipeline(es.split(), new OsxSecurityParsingStream());
}
Loading

0 comments on commit 523519c

Please sign in to comment.