Include azure-account extension

This commit is contained in:
Christof Marti
2017-08-18 14:47:45 -07:00
parent abea60daf8
commit b445cded34
10 changed files with 709 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ npmInstall('extensions'); // node modules shared by all extensions
const extensions = [
'vscode-api-tests',
'vscode-colorize-tests',
'azure-account',
'json',
'configuration-editing',
'extension-editing',

View File

@@ -0,0 +1,4 @@
test/**
src/**
tsconfig.json
npm-shrinkwrap.json

View File

@@ -0,0 +1,70 @@
{
"name": "azure-account",
"version": "0.1.0",
"publisher": "vscode",
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"activationEvents": [
"*"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "azure-account.login",
"title": "%azure-account.commands.login%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.logout",
"title": "%azure-account.commands.logout%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.addFilter",
"title": "%azure-account.commands.addResourceFilter%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.removeFilter",
"title": "%azure-account.commands.removeResourceFilter%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.createAccount",
"title": "%azure-account.commands.createAccount%",
"category": "%azure-account.commands.azure%"
}
],
"configuration": {
"type": "object",
"title": "Azure configuration",
"properties": {
"azure.resourceFilter": {
"type": "array",
"default": null,
"description": "The resource filter, each element is either a subscription id or a subscription id and a resource group name separated by a slash."
}
}
}
},
"scripts": {
"compile": "gulp compile-extension:azure-account",
"watch": "gulp watch-extension:azure-account"
},
"devDependencies": {
"@types/copy-paste": "^1.1.30",
"@types/node": "^6.0.40",
"@types/opn": "^3.0.28"
},
"dependencies": {
"adal-node": "^0.1.22",
"azure-arm-resource": "^2.0.0-preview",
"copy-paste": "^1.3.0",
"ms-rest-azure": "^2.2.3",
"vscode-nls": "^2.0.2",
"opn": "^5.1.0"
}
}

View File

@@ -0,0 +1,8 @@
{
"azure-account.commands.azure": "Azure",
"azure-account.commands.login": "Login",
"azure-account.commands.logout": "Logout",
"azure-account.commands.addResourceFilter": "Add Resource Filter",
"azure-account.commands.removeResourceFilter": "Remove Resource Filter",
"azure-account.commands.createAccount": "Create an Account"
}

View File

@@ -0,0 +1,471 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const adal = require('adal-node');
const MemoryCache = adal.MemoryCache;
const AuthenticationContext = adal.AuthenticationContext;
const CacheDriver = require('adal-node/lib/cache-driver');
const createLogContext = require('adal-node/lib/log').createLogContext;
import { DeviceTokenCredentials, AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource';
import * as opn from 'opn';
import * as copypaste from 'copy-paste';
import * as nls from 'vscode-nls';
import { window, commands, credentials, EventEmitter, MessageItem, ExtensionContext, workspace, ConfigurationTarget } from 'vscode';
import { AzureLogin, AzureSession, AzureLoginStatus, AzureResourceFilter } from './typings/azure-account.api';
const localize = nls.loadMessageBundle();
const defaultEnvironment = (<any>AzureEnvironment).Azure;
const commonTenantId = 'common';
const authorityHostUrl = defaultEnvironment.activeDirectoryEndpointUrl;
const clientId = '818dee8b-8777-4f45-afc3-f7cc977caae2';
const authorityUrl = `${authorityHostUrl}${commonTenantId}`;
const resource = defaultEnvironment.activeDirectoryResourceId;
const credentialsService = 'VSCode Public Azure';
const credentialsAccount = 'Refresh Token';
interface DeviceLogin {
userCode: string;
deviceCode: string;
verificationUrl: string;
expiresIn: number;
interval: number;
message: string;
}
interface TokenResponse {
tokenType: string;
expiresIn: number;
expiresOn: string;
resource: string;
accessToken: string;
refreshToken: string;
userId: string;
isUserIdDisplayable: boolean;
familyName: string;
givenName: string;
oid: string;
tenantId: string;
isMRRT: boolean;
_clientId: string;
_authority: string;
}
interface AzureLoginWriteable extends AzureLogin {
status: AzureLoginStatus;
}
class AzureLoginError extends Error {
constructor(message: string, public _reason: any) {
super(message);
}
}
export class AzureLoginHelper {
private onStatusChanged = new EventEmitter<AzureLoginStatus>();
private onSessionsChanged = new EventEmitter<void>();
private onFiltersChanged = new EventEmitter<void>();
private tokenCache = new MemoryCache();
private oldResourceFilter: string;
constructor(context: ExtensionContext) {
const subscriptions = context.subscriptions;
subscriptions.push(commands.registerCommand('azure-account.login', () => this.login().catch(console.error)));
subscriptions.push(commands.registerCommand('azure-account.logout', () => this.logout().catch(console.error)));
subscriptions.push(commands.registerCommand('azure-account.askForLogin', () => this.askForLogin().catch(console.error)));
subscriptions.push(commands.registerCommand('azure-account.addFilter', () => this.addFilter().catch(console.error)));
subscriptions.push(commands.registerCommand('azure-account.removeFilter', () => this.removeFilter().catch(console.error)));
subscriptions.push(this.api.onSessionsChanged(() => this.updateFilters().catch(console.error)));
subscriptions.push(workspace.onDidChangeConfiguration(() => this.updateFilters(true).catch(console.error)));
this.initialize()
.catch(console.error);
}
api: AzureLogin = {
status: 'Initializing',
onStatusChanged: this.onStatusChanged.event,
sessions: [],
onSessionsChanged: this.onSessionsChanged.event,
filters: [],
onFiltersChanged: this.onFiltersChanged.event
};
async login() {
try {
this.beginLoggingIn();
const deviceLogin = await deviceLogin1();
const copyAndOpen: MessageItem = { title: localize('azure-account.copyAndOpen', "Copy & Open") };
const close: MessageItem = { title: localize('azure-account.close', "Close"), isCloseAffordance: true };
const response = await window.showInformationMessage(deviceLogin.message, copyAndOpen, close);
if (response === copyAndOpen) {
copypaste.copy(deviceLogin.userCode);
opn(deviceLogin.verificationUrl);
}
const tokenResponse = await deviceLogin2(deviceLogin);
const refreshToken = tokenResponse.refreshToken;
const tokenResponses = await tokensFromToken(tokenResponse);
await credentials.writeSecret(credentialsService, credentialsAccount, refreshToken);
await this.updateSessions(tokenResponses);
} finally {
this.updateStatus();
}
}
async logout() {
await credentials.deleteSecret(credentialsService, credentialsAccount);
await this.updateSessions([]);
this.updateStatus();
}
private async initialize() {
try {
const refreshToken = await credentials.readSecret(credentialsService, credentialsAccount);
if (refreshToken) {
this.beginLoggingIn();
const tokenResponse = await tokenFromRefreshToken(refreshToken);
const tokenResponses = await tokensFromToken(tokenResponse);
await this.updateSessions(tokenResponses);
}
} catch (err) {
if (!(err instanceof AzureLoginError)) {
throw err;
}
} finally {
this.updateStatus();
}
}
private beginLoggingIn() {
if (this.api.status !== 'LoggedIn') {
(<AzureLoginWriteable>this.api).status = 'LoggingIn';
this.onStatusChanged.fire(this.api.status);
}
}
private updateStatus() {
const status = this.api.sessions.length ? 'LoggedIn' : 'LoggedOut';
if (this.api.status !== status) {
(<AzureLoginWriteable>this.api).status = status;
this.onStatusChanged.fire(this.api.status);
}
}
private async updateSessions(tokenResponses: TokenResponse[]) {
await clearTokenCache(this.tokenCache);
for (const tokenResponse of tokenResponses) {
await addTokenToCache(this.tokenCache, tokenResponse);
}
const sessions = this.api.sessions;
sessions.splice(0, sessions.length, ...tokenResponses.map<AzureSession>(tokenResponse => ({
environment: defaultEnvironment,
userId: tokenResponse.userId,
tenantId: tokenResponse.tenantId,
credentials: new DeviceTokenCredentials({ username: tokenResponse.userId, clientId, tokenCache: this.tokenCache, domain: tokenResponse.tenantId })
})));
this.onSessionsChanged.fire();
}
private async askForLogin() {
if (this.api.status === 'LoggedIn') {
return;
}
const login = { title: localize('azure-account.login', "Login") };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showInformationMessage(localize('azure-account.loginFirst', "Not logged in, log in first."), login, cancel);
return result === login && commands.executeCommand('azure-account.login');
}
private async addFilter() {
if (this.api.status !== 'LoggedIn') {
return commands.executeCommand('azure-account.askForLogin');
}
const azureConfig = workspace.getConfiguration('azure');
const resourceFilter = azureConfig.get<string[]>('resourceFilter') || [];
const subscriptionItems: { session: AzureSession; subscription: SubscriptionModels.Subscription }[] = [];
for (const session of this.api.sessions) {
const credentials = session.credentials;
const client = new SubscriptionClient(credentials);
const subscriptions = await listAll(client.subscriptions, client.subscriptions.list());
subscriptionItems.push(...subscriptions.filter(subscription => resourceFilter.indexOf(`${session.tenantId}/${subscription.subscriptionId}`) === -1)
.map(subscription => ({
session,
subscription
})));
}
subscriptionItems.sort((a, b) => a.subscription.displayName!.localeCompare(b.subscription.displayName!));
const subscriptionResult = await window.showQuickPick(subscriptionItems.map(subscription => ({
label: subscription.subscription.displayName!,
description: subscription.subscription.subscriptionId!,
subscription
})));
if (!subscriptionResult) {
return;
}
const { session, subscription } = subscriptionResult.subscription;
const client = new ResourceManagementClient(session.credentials, subscription.subscriptionId!);
const resourceGroups = await listAll(client.resourceGroups, client.resourceGroups.list());
const resourceGroupFilters: AzureResourceFilter[] = [
{
...subscriptionResult.subscription,
allResourceGroups: true,
resourceGroups
}
];
resourceGroupFilters.push(...resourceGroups.filter(resourceGroup => resourceFilter.indexOf(`${session.tenantId}/${subscription.subscriptionId}/${resourceGroup.name}`) === -1)
.map(resourceGroup => ({
session,
subscription,
allResourceGroups: false,
resourceGroups: [resourceGroup]
})));
resourceGroupFilters.sort((a, b) => (!a.allResourceGroups ? a.resourceGroups[0].name! : '').localeCompare(!b.allResourceGroups ? b.resourceGroups[0].name! : ''));
const resourceGroupResult = await window.showQuickPick(resourceGroupFilters.map(resourceGroup => (!resourceGroup.allResourceGroups ? {
label: resourceGroup.resourceGroups[0].name!,
description: resourceGroup.resourceGroups[0].location,
resourceGroup
} : {
label: localize('azure-account.entireSubscription', "Entire Subscription"),
description: '',
resourceGroup
})));
if (!resourceGroupResult) {
return;
}
const resourceGroup = resourceGroupResult.resourceGroup;
if (!resourceGroup.allResourceGroups) {
resourceFilter.push(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}/${resourceGroup.resourceGroups[0].name}`);
} else {
resourceFilter.splice(0, resourceFilter.length, ...resourceFilter.filter(c => !c.startsWith(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}/`)));
resourceFilter.push(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}`);
}
const resourceFilterConfig = azureConfig.inspect<string[]>('resourceFilter');
let target = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
target = ConfigurationTarget.WorkspaceFolder;
} else if (resourceFilterConfig.workspaceValue) {
target = ConfigurationTarget.Workspace;
} else if (resourceFilterConfig.globalValue) {
target = ConfigurationTarget.Global;
}
}
await azureConfig.update('resourceFilter', resourceFilter, target);
}
private async removeFilter() {
if (this.api.status !== 'LoggedIn') {
return commands.executeCommand('azure-account.askForLogin');
}
const azureConfig = workspace.getConfiguration('azure');
let resourceFilter = azureConfig.get<string[]>('resourceFilter') || [];
const filters = resourceFilter.length ? this.api.filters.reduce((list, filter) => {
if (filter.allResourceGroups) {
list.push(filter);
} else {
list.push(...filter.resourceGroups.map(resourceGroup => ({
...filter,
resourceGroups: [resourceGroup]
})));
}
return list;
}, <AzureResourceFilter[]>[]) : [];
filters.sort((a, b) => (!a.allResourceGroups ? a.resourceGroups[0].name! : `/${a.subscription.displayName}`).localeCompare(!b.allResourceGroups ? b.resourceGroups[0].name! : `/${b.subscription.displayName}`));
const filterResult = await window.showQuickPick(filters.map(filter => (!filter.allResourceGroups ? {
label: filter.resourceGroups[0].name!,
description: filter.subscription.displayName!,
filter
} : {
label: filter.subscription.displayName!,
description: filter.subscription.subscriptionId!,
filter
})));
if (!filterResult) {
return;
}
const filter = filterResult.filter;
const remove = !filter.allResourceGroups ?
`${filter.session.tenantId}/${filter.subscription.subscriptionId}/${filter.resourceGroups[0].name}` :
`${filter.session.tenantId}/${filter.subscription.subscriptionId}`;
resourceFilter = resourceFilter.filter(e => e !== remove);
const resourceFilterConfig = azureConfig.inspect<string[]>('resourceFilter');
let target = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
target = ConfigurationTarget.WorkspaceFolder;
} else if (resourceFilterConfig.workspaceValue) {
target = ConfigurationTarget.Workspace;
} else if (resourceFilterConfig.globalValue) {
target = ConfigurationTarget.Global;
}
}
await azureConfig.update('resourceFilter', resourceFilter.length ? resourceFilter : undefined, target);
}
private async updateFilters(configChange = false) {
const azureConfig = workspace.getConfiguration('azure');
let resourceFilter = azureConfig.get<string[]>('resourceFilter');
if (configChange && JSON.stringify(resourceFilter) === this.oldResourceFilter) {
return;
}
this.oldResourceFilter = JSON.stringify(resourceFilter);
if (resourceFilter && !Array.isArray(resourceFilter)) {
resourceFilter = [];
}
const filters = resourceFilter && resourceFilter.map(s => typeof s === 'string' ? s.split('/') : [])
.filter(s => s.length === 2 || s.length === 3)
.map(([tenantId, subscriptionId, resourceGroup]) => ({ tenantId, subscriptionId, resourceGroup }));
const tenantIds = filters && filters.reduce<Record<string, Record<string, Record<string, boolean> | boolean>>>((result, filter) => {
const tenant = result[filter.tenantId] || (result[filter.tenantId] = {});
const resourceGroups = tenant[filter.subscriptionId] || (tenant[filter.subscriptionId] = (filter.resourceGroup ? {} : true));
if (typeof resourceGroups === 'object' && filter.resourceGroup) {
resourceGroups[filter.resourceGroup] = true;
}
return result;
}, {});
const newFilters: AzureResourceFilter[] = [];
const sessions = tenantIds ? this.api.sessions.filter(session => tenantIds[session.tenantId]) : this.api.sessions;
for (const session of sessions) {
const client = new SubscriptionClient(session.credentials);
const subscriptionIds = tenantIds && tenantIds[session.tenantId];
const subscriptions = await listAll(client.subscriptions, client.subscriptions.list());
const filteredSubscriptions = subscriptionIds ? subscriptions.filter(subscription => subscriptionIds[subscription.subscriptionId!]) : subscriptions;
for (const subscription of filteredSubscriptions) {
const client = new ResourceManagementClient(session.credentials, subscription.subscriptionId!);
const resourceGroupNames = subscriptionIds && subscriptionIds[subscription.subscriptionId!];
const allResourceGroups = !(resourceGroupNames && typeof resourceGroupNames === 'object');
const unfilteredResourceGroups = await listAll(client.resourceGroups, client.resourceGroups.list());
const resourceGroups = allResourceGroups ? unfilteredResourceGroups : unfilteredResourceGroups.filter(resourceGroup => (<Record<string, boolean>>resourceGroupNames!)[resourceGroup.name!]);
newFilters.push({ session, subscription, allResourceGroups, resourceGroups });
}
}
this.api.filters.splice(0, this.api.filters.length, ...newFilters);
this.onFiltersChanged.fire();
}
}
async function deviceLogin1(): Promise<DeviceLogin> {
return new Promise<DeviceLogin>((resolve, reject) => {
const cache = new MemoryCache();
const context = new AuthenticationContext(authorityUrl, null, cache);
context.acquireUserCode(resource, clientId, 'en-us', function (err: any, response: any) {
if (err) {
reject(new AzureLoginError(localize('azure-account.userCodeFailed', "Aquiring user code failed"), err));
} else {
resolve(response);
}
});
});
}
async function deviceLogin2(deviceLogin: DeviceLogin) {
return new Promise<TokenResponse>((resolve, reject) => {
const tokenCache = new MemoryCache();
const context = new AuthenticationContext(authorityUrl, null, tokenCache);
context.acquireTokenWithDeviceCode(resource, clientId, deviceLogin, function (err: any, tokenResponse: TokenResponse) {
if (err) {
reject(new AzureLoginError(localize('azure-account.tokenFailed', "Aquiring token with device code"), err));
} else {
resolve(tokenResponse);
}
});
});
}
async function tokenFromRefreshToken(refreshToken: string, tenantId = commonTenantId) {
return new Promise<TokenResponse>((resolve, reject) => {
const tokenCache = new MemoryCache();
const context = new AuthenticationContext(`${authorityHostUrl}${tenantId}`, null, tokenCache);
context.acquireTokenWithRefreshToken(refreshToken, clientId, null, function (err: any, tokenResponse: TokenResponse) {
if (err) {
reject(new AzureLoginError(localize('azure-account.tokenFromRefreshTokenFailed', "Aquiring token with refresh token"), err));
} else {
resolve(tokenResponse);
}
});
});
}
async function tokensFromToken(firstTokenResponse: TokenResponse) {
const tokenResponses = [firstTokenResponse];
const tokenCache = new MemoryCache();
await addTokenToCache(tokenCache, firstTokenResponse);
const credentials = new DeviceTokenCredentials({ username: firstTokenResponse.userId, clientId, tokenCache });
const client = new SubscriptionClient(credentials);
const tenants = await listAll(client.tenants, client.tenants.list());
for (const tenant of tenants) {
if (tenant.tenantId !== firstTokenResponse.tenantId) {
const tokenResponse = await tokenFromRefreshToken(firstTokenResponse.refreshToken, tenant.tenantId);
tokenResponses.push(tokenResponse);
}
}
return tokenResponses;
}
async function addTokenToCache(tokenCache: any, tokenResponse: TokenResponse) {
return new Promise<any>((resolve, reject) => {
const driver = new CacheDriver(
{ _logContext: createLogContext('') },
`${authorityHostUrl}${tokenResponse.tenantId}`,
tokenResponse.resource,
clientId,
tokenCache,
(entry: any, resource: any, callback: (err: any, response: any) => {}) => {
callback(null, entry);
}
);
driver.add(tokenResponse, function (err: any) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async function clearTokenCache(tokenCache: any) {
await new Promise<void>((resolve, reject) => {
tokenCache.find({}, (err: any, entries: any[]) => {
if (err) {
reject(err);
} else {
tokenCache.remove(entries, (err: any) => {
if (err) {
reject(err);
} else {
resolve();
}
});
}
});
});
}
export interface PartialList<T> extends Array<T> {
nextLink?: string;
}
export async function listAll<T>(client: { listNext(nextPageLink: string): Promise<PartialList<T>>; }, first: Promise<PartialList<T>>): Promise<T[]> {
const all: T[] = [];
for (let list = await first; list.length || list.nextLink; list = list.nextLink ? await client.listNext(list.nextLink) : []) {
all.push(...list);
}
return all;
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, ExtensionContext, commands, credentials } from 'vscode';
import { AzureLoginHelper } from './azure-account';
import { AzureLogin } from './typings/azure-account.api';
import * as opn from 'opn';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export function activate(context: ExtensionContext) {
if (!credentials) {
return; // Proposed API not available.
}
const azureLogin = new AzureLoginHelper(context);
const subscriptions = context.subscriptions;
subscriptions.push(createStatusBarItem(azureLogin.api));
subscriptions.push(commands.registerCommand('azure-account.createAccount', createAccount));
return azureLogin.api;
}
function createAccount() {
opn('https://azure.microsoft.com/en-us/free');
}
function createStatusBarItem(api: AzureLogin) {
const statusBarItem = window.createStatusBarItem();
function updateStatusBar() {
switch (api.status) {
case 'LoggingIn':
statusBarItem.text = localize('azure-account.loggingIn', "Azure: Logging in...");
statusBarItem.show();
break;
case 'LoggedIn':
statusBarItem.text = localize('azure-account.loggedIn', "Azure: {0}", api.sessions[0].userId);
statusBarItem.show();
break;
default:
statusBarItem.hide();
break;
}
}
api.onStatusChanged(updateStatusBar);
api.onSessionsChanged(updateStatusBar);
updateStatusBar();
return statusBarItem;
}
export function deactivate() {
}

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionModels, ResourceModels } from 'azure-arm-resource';
export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut';
export interface AzureLogin {
readonly status: AzureLoginStatus;
readonly onStatusChanged: Event<AzureLoginStatus>;
readonly sessions: AzureSession[];
readonly onSessionsChanged: Event<void>;
readonly filters: AzureResourceFilter[];
readonly onFiltersChanged: Event<void>;
}
export interface AzureSession {
readonly environment: AzureEnvironment;
readonly userId: string;
readonly tenantId: string;
readonly credentials: ServiceClientCredentials;
}
export interface AzureResourceFilter {
readonly session: AzureSession;
readonly subscription: SubscriptionModels.Subscription;
readonly allResourceGroups: boolean;
readonly resourceGroups: ResourceModels.ResourceGroup[];
}

View File

@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This is the place for API experiments and proposal.
declare module 'vscode' {
/**
* Namespace for handling credentials.
*/
export namespace credentials {
/**
* Read a previously stored secret from the credential store.
*
* @param service The service of the credential.
* @param account The account of the credential.
* @return A promise for the secret of the credential.
*/
export function readSecret(service: string, account: string): Thenable<string | undefined>;
/**
* Write a secret to the credential store.
*
* @param service The service of the credential.
* @param account The account of the credential.
* @param secret The secret of the credential to write to the credential store.
* @return A promise indicating completion of the operation.
*/
export function writeSecret(service: string, account: string, secret: string): Thenable<void>;
/**
* Delete a previously stored secret from the credential store.
*
* @param service The service of the credential.
* @param account The account of the credential.
* @return A promise resolving to true if there was a secret for that service and account.
*/
export function deleteSecret(service: string, account: string): Thenable<boolean>;
}
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"strict": true,
"noUnusedLocals": true,
"outDir": "./out",
"lib": [
"es6"
],
"sourceMap": true
},
"exclude": [
"node_modules"
],
"include": [
"src/**/*"
]
}