UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

176 lines (175 loc) 6.98 kB
"use strict"; // // Implementation of token store that reads and writes to the Windows credential store. // Uses included "creds.exe" program to access the credential store. // Object.defineProperty(exports, "__esModule", { value: true }); exports.createWinTokenStore = exports.WinTokenStore = void 0; const childProcess = require("child_process"); const rxjs_1 = require("rxjs"); const split = require("split2"); const through = require("through2"); const path = require("path"); const parser = require("./win-credstore-parser"); const debug = require("debug")("appcenter-cli:util:token-store:win32:win-token-store"); const util_1 = require("util"); const credExePath = path.join(__dirname, "../../../../bin/windows/creds.exe"); const targetNamePrefix = "AppCenterCli:target="; const oldTargetNamePrefix = "MobileCenterCli:target="; class Prefixer { constructor(useOldName) { this.prefix = useOldName ? oldTargetNamePrefix : targetNamePrefix; } ensurePrefix(targetName) { if (targetName.slice(this.prefix.length) !== this.prefix) { targetName = this.prefix + targetName; } return targetName; } removePrefix(targetName) { return targetName.slice(this.prefix.length); } removePrefixFromCred(cred) { if (cred.targetName) { cred.targetName = this.removePrefix(cred.targetName); } return cred; } } function encodeTokenValueAsHex(token) { const tokenValueAsString = JSON.stringify(token); return Buffer.from(tokenValueAsString, "utf8").toString("hex"); } function decodeTokenValueFromHex(token) { return JSON.parse(Buffer.from(token, "hex").toString("utf8")); } function credToTokenEntry(cred) { // Assumes credential comes in with prefixes on target skipped, and // Credential object in hexidecimal debug(`Converting credential ${util_1.inspect(cred)} to TokenEntry`); return { key: cred.targetName, accessToken: decodeTokenValueFromHex(cred.credential), }; } class WinTokenStore { /** * list the contents of the credential store, parsing each value. * * We ignore everything that wasn't put there by us, we look * for target names starting with the target name prefix. * * * @return {Observable<TokenEntry>} stream of credentials. */ list() { const prefixer = new Prefixer(false); return rxjs_1.Observable.create((observer) => { const credsProcess = childProcess.spawn(credExePath, ["-s", "-g", "-t", `${targetNamePrefix}*`]); debug("Creds process started for list, monitoring output"); const credStream = credsProcess.stdout.pipe(parser.createParsingStream()).pipe(through.obj(function (chunk, enc, done) { done(null, prefixer.removePrefixFromCred(chunk)); })); credStream.on("data", (cred) => { debug(`Got data from creds: ${cred}`); observer.next(credToTokenEntry(cred)); }); credStream.on("end", () => { debug(`output list completed`); observer.complete(); }); credStream.on("error", (err) => observer.error(err)); }); } /** * Get details for a specific credential. Assumes generic credential. * * @param {tokenKeyType} key target name for credential * @return {Promise<TokenEntry>} Returned credential or null if not found. */ get(key, useOldName = false) { const prefixer = new Prefixer(useOldName); const args = ["-s", "-t", prefixer.ensurePrefix(key)]; const credsProcess = childProcess.spawn(credExePath, args); let result = null; const errors = []; debug(`Getting key with args ${util_1.inspect(args)}`); return new Promise((resolve, reject) => { credsProcess.stdout .pipe(parser.createParsingStream()) .pipe(through.obj(function (chunk, enc, done) { done(null, prefixer.removePrefixFromCred(chunk)); })) .on("data", (credential) => { result = credential; result.targetName = prefixer.removePrefix(result.targetName); }); credsProcess.stderr.pipe(split()).on("data", (line) => { errors.push(line); }); credsProcess.on("exit", (code) => { if (code === 0) { debug(`Completed getting token, result = ${util_1.inspect(result)}`); return resolve(credToTokenEntry(result)); } return reject(new Error(`Getting credential failed, exit code ${code}: ${errors.join(", ")}`)); }); }); } /** * Set the credential for a given key in the credential store. * Creates or updates, assumes generic credential. * * @param {TokenKeyType} key key for entry (string user name for now) * @param {TokenValueType} credential the credential to be encrypted * * @return {Promise<void>} Promise that completes when update has finished * @param {Function(err)} callback completion callback */ set(key, credential) { const prefixer = new Prefixer(false); const args = ["-a", "-t", prefixer.ensurePrefix(key), "-p", encodeTokenValueAsHex(credential)]; debug(`Saving token with args ${util_1.inspect(args)}`); return new Promise((resolve, reject) => { childProcess.execFile(credExePath, args, function (err) { if (err) { debug(`Token store failed, ${util_1.inspect(err)}`); return reject(err); } debug(`Token successfully stored`); return resolve(); }); }); } /** * Remove the given key from the credential store. * * @param {TokenKeyType} key target name to remove. * if ends with "*" character, * will delete all targets * starting with that prefix * @param {Function(err)} callback completion callback */ remove(key) { const prefixer = new Prefixer(false); const args = ["-d", "-t", prefixer.ensurePrefix(key)]; if (key.slice(-1) === "*") { args.push("-g"); } debug(`Deleting token with args ${util_1.inspect(args)}`); return new Promise((resolve, reject) => { childProcess.execFile(credExePath, args, function (err) { if (err) { return reject(err); } resolve(); }); }); } } exports.WinTokenStore = WinTokenStore; function createWinTokenStore() { debug(`Creating WinTokenStore`); return new WinTokenStore(); } exports.createWinTokenStore = createWinTokenStore;