-
Notifications
You must be signed in to change notification settings - Fork 12
/
index.ts
128 lines (107 loc) · 4.25 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import * as chalk from "chalk";
import { MsoIssuer, MsoDeviceCodeClientMedata } from "./authentication";
import { custom, Client } from "openid-client";
import { UserNpmConfig, ProjectNpmConfig } from "./npm-config";
import { UserYarnConfig, ProjectYarnConfig } from "./yarn-config";
import { resolve } from "path";
import * as fs from 'fs';
import * as path from 'path';
const AZDEVOPS_RESOURCE_ID = "499b84ac-1321-427f-aa17-267ca6975798";
const AZDEVOPS_AUTH_CLIENT_ID = "f9d5fef7-a410-4582-bb27-68a319b1e5a1";
const AZDEVOPS_AUTH_TENANT_ID = "common";
const CI_DEFAULT_ENV_VARIABLE_NAME = "TF_BUILD";
export function inCI(ciInfo: boolean | string) {
if (!ciInfo) {
return false;
}
const variableName =
typeof ciInfo === "string" ? ciInfo : CI_DEFAULT_ENV_VARIABLE_NAME;
if (!process.env[variableName]) {
return false;
}
console.log("Skipped auth due to running in CI environment");
return true;
}
async function run(
clientId = AZDEVOPS_AUTH_CLIENT_ID,
tenantId = AZDEVOPS_AUTH_TENANT_ID,
ciInfo: boolean | string,
projectBasePath?: string
) {
if (inCI(ciInfo)) {
return;
}
const resolvedProjectBasePath = projectBasePath ? resolve(projectBasePath) : process.cwd();
const isProjectUsingYarnv2 = fs.existsSync(path.join(resolvedProjectBasePath, ".yarnrc.yml"));
const userConfig = !isProjectUsingYarnv2 ? new UserNpmConfig() : new UserYarnConfig();
const projectConfig = !isProjectUsingYarnv2 ? new ProjectNpmConfig(resolvedProjectBasePath) : new ProjectYarnConfig(resolvedProjectBasePath);
for (const registry of getRegistries(userConfig, projectConfig)) {
console.log(chalk.green(`Found registry ${registry}`));
const issuer = await MsoIssuer.discover(tenantId);
const client = new issuer.Client(new MsoDeviceCodeClientMedata(clientId));
// Set timeout to 5s to workaround issue #18
// https://github.com/gsoft-inc/azure-devops-npm-auth/issues/18
client[custom.http_options] = function (options) {
options.timeout = 5000;
return options;
}
let tokenSet;
const refreshToken = userConfig.getRegistryRefreshToken(registry);
if (refreshToken) {
try {
console.log("Trying to use refresh token...");
tokenSet = await client.refresh(refreshToken);
} catch (exception) {
switch (exception.error) {
case "invalid_grant":
console.log(chalk.yellow("Refresh token is invalid or expired."));
tokenSet = await startDeviceCodeFlow(client);
break;
case "interaction_required":
console.log(chalk.yellow("Interaction required."));
tokenSet = await startDeviceCodeFlow(client);
break;
default:
throw exception;
}
}
} else {
tokenSet = await startDeviceCodeFlow(client);
}
// Update user npm config with tokens
userConfig.setRegistryAuthToken(registry, tokenSet.access_token);
userConfig.setRegistryRefreshToken(registry, tokenSet.refresh_token);
console.log(
chalk.green(`Done! You can now install packages from ${registry} \n`)
);
}
}
async function startDeviceCodeFlow(client: Client) {
console.log(chalk.green("Launching device code authentication..."));
// Make sure to include 'offline_access' scope to receive refresh token.
const handle = await client.deviceAuthorization({
scope: `${AZDEVOPS_RESOURCE_ID}/.default offline_access`
});
console.log(
`To sign in, use a web browser to open the page ${handle.verification_uri} and enter the code ${handle.user_code} to authenticate.`
);
return await handle.poll();
}
function getRegistries(userConfig: UserNpmConfig | UserYarnConfig, projectConfig: ProjectNpmConfig | ProjectYarnConfig) {
// Registries should be set on project level but fallback to user defined.
const projectRegistries = projectConfig.getRegistries();
const userRegistries = userConfig.getRegistries();
const registries = (projectRegistries.length !== 0
? projectRegistries
: userRegistries
)
// return unique list of registries
.filter((key, index, keys) => index === keys.indexOf(key));
if (registries.length === 0) {
throw new Error(
"No private registry defined in project .npmrc or user defined .npmrc."
);
}
return registries;
}
export { run };