dataverse-utils 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.json CHANGED
@@ -2,7 +2,22 @@
2
2
  "name": "dataverse-utils",
3
3
  "entries": [
4
4
  {
5
- "date": "Thu, 02 Sep 2021 16:21:26 GMT",
5
+ "date": "Thu, 16 Sep 2021 20:49:35 GMT",
6
+ "tag": "dataverse-utils_v2.0.0",
7
+ "version": "2.0.0",
8
+ "comments": {
9
+ "major": [
10
+ {
11
+ "comment": "Switch to device-code auth",
12
+ "author": "derek.finlinson@journeyteam.com",
13
+ "commit": "b1c526bdddb59fe2f4a3eb61096909ebbb2379d7",
14
+ "package": "dataverse-utils"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Thu, 02 Sep 2021 16:22:00 GMT",
6
21
  "tag": "dataverse-utils_v1.0.1",
7
22
  "version": "1.0.1",
8
23
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,13 +1,21 @@
1
1
  # Change Log - dataverse-utils
2
2
 
3
- This log was last generated on Thu, 02 Sep 2021 16:21:26 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 16 Sep 2021 20:49:35 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
- ## 1.0.1
7
+ ## 2.0.0
8
8
 
9
- Thu, 02 Sep 2021 16:21:26 GMT
9
+ Thu, 16 Sep 2021 20:49:35 GMT
10
10
 
11
- ### Patches
11
+ ### Major changes
12
12
 
13
+ - Switch to device-code auth (derek.finlinson@journeyteam.com)
14
+
15
+ ## 1.0.1
16
+
17
+ Thu, 02 Sep 2021 16:22:00 GMT
18
+
19
+ ### Patches
20
+
13
21
  - Add generate command; Rename project (derek.finlinson@journeyteam.com)
package/README.md CHANGED
@@ -5,17 +5,9 @@
5
5
 
6
6
  Utilities for interacting with Dataverse environments
7
7
 
8
- # Acquire authorization token
9
-
10
- Connection information is stored in dataverse.config.json.
11
-
12
- ```sh
13
- dataverse-utils auth
14
- ```
15
-
16
8
  # Deploy
17
9
 
18
- Deployment configuration is stored in dataverse.config.json.
10
+ Deployment configuration is stored in dataverse.config.json. Access token will be acquired via device-code flow.
19
11
 
20
12
  ```sh
21
13
  dataverse-utils deploy webresource
@@ -25,7 +17,7 @@ dataverse-utils deploy assembly
25
17
 
26
18
  # Generate Early-Bound TS Files
27
19
 
28
- Generate early bound TypeScript files for tables
20
+ Generate early bound TypeScript files for tables. Access token will be acquired via device-code flow.
29
21
 
30
22
  ```sh
31
23
  dataverse-utils generate account
package/lib/auth.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { AuthenticationResult } from '@azure/msal-node';
2
+ export declare const getAccessToken: (tenant: string, url: string) => Promise<AuthenticationResult | null>;
package/lib/auth.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAccessToken = void 0;
4
+ const cachePlugin_1 = require("./cachePlugin");
5
+ const msal_node_1 = require("@azure/msal-node");
6
+ const just_scripts_utils_1 = require("just-scripts-utils");
7
+ const clientId = '51f81489-12ee-4a9e-aaae-a2591f45987d';
8
+ const getAccessToken = async (tenant, url) => {
9
+ const config = {
10
+ auth: {
11
+ clientId: clientId,
12
+ authority: `https://login.microsoftonline.com/${tenant}/`
13
+ },
14
+ cache: {
15
+ cachePlugin: cachePlugin_1.cachePlugin(url.replace('https://', '').split('.')[0])
16
+ }
17
+ };
18
+ const pca = new msal_node_1.PublicClientApplication(config);
19
+ const cache = pca.getTokenCache();
20
+ const accounts = await (cache === null || cache === void 0 ? void 0 : cache.getAllAccounts().catch(ex => {
21
+ throw new Error(ex.message);
22
+ }));
23
+ // Try to get token silently
24
+ if (accounts.length > 0) {
25
+ const silentToken = await pca.acquireTokenSilent({
26
+ account: accounts[0],
27
+ scopes: [`${url}/.default`]
28
+ }).catch(ex => {
29
+ throw new Error(ex.message);
30
+ });
31
+ if (silentToken) {
32
+ return silentToken;
33
+ }
34
+ }
35
+ // Acquire token by device code
36
+ const token = await pca.acquireTokenByDeviceCode({
37
+ scopes: [`${url}/.default`],
38
+ deviceCodeCallback: (response) => just_scripts_utils_1.logger.info(response.message)
39
+ }).catch(ex => {
40
+ throw new Error(ex.message);
41
+ });
42
+ return token;
43
+ };
44
+ exports.getAccessToken = getAccessToken;
@@ -0,0 +1,2 @@
1
+ import { ICachePlugin } from '@azure/msal-node';
2
+ export declare function cachePlugin(org: string): ICachePlugin;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cachePlugin = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ function cachePlugin(org) {
11
+ const getCachePath = () => {
12
+ if (!fs_1.default.existsSync(path_1.default.join(os_1.default.homedir(), './.dataverse-utils/'))) {
13
+ fs_1.default.mkdirSync(path_1.default.join(os_1.default.homedir(), './.dataverse-utils/'));
14
+ }
15
+ return path_1.default.join(os_1.default.homedir(), `./.dataverse-utils/${org}.json`);
16
+ };
17
+ const cacheLocation = getCachePath();
18
+ const beforeCacheAccess = (tokenCacheContext) => {
19
+ return new Promise((resolve, reject) => {
20
+ if (fs_1.default.existsSync(cacheLocation)) {
21
+ fs_1.default.readFile(cacheLocation, 'utf-8', (err, data) => {
22
+ if (err) {
23
+ reject();
24
+ }
25
+ else {
26
+ // const crypt = new Cryptr(os.userInfo.name);
27
+ // const decrypted = crypt.decrypt(data.toString());
28
+ tokenCacheContext.tokenCache.deserialize(data);
29
+ resolve();
30
+ }
31
+ });
32
+ }
33
+ else {
34
+ fs_1.default.writeFile(cacheLocation, tokenCacheContext.tokenCache.serialize(), (err) => {
35
+ if (err) {
36
+ reject();
37
+ }
38
+ });
39
+ }
40
+ });
41
+ };
42
+ const afterCacheAccess = (tokenCacheContext) => {
43
+ return new Promise((resolve, reject) => {
44
+ if (tokenCacheContext.cacheHasChanged) {
45
+ // const crypt = new Cryptr(os.userInfo.name);
46
+ // const encrypted = crypt.encrypt(tokenCacheContext.tokenCache.serialize());
47
+ fs_1.default.writeFile(cacheLocation, tokenCacheContext.tokenCache.serialize(), (err) => {
48
+ if (err) {
49
+ reject(err);
50
+ }
51
+ resolve();
52
+ });
53
+ }
54
+ else {
55
+ resolve();
56
+ }
57
+ });
58
+ };
59
+ return {
60
+ beforeCacheAccess,
61
+ afterCacheAccess
62
+ };
63
+ }
64
+ exports.cachePlugin = cachePlugin;
@@ -5,36 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
- const child_process_1 = require("child_process");
9
8
  const deploy_1 = __importDefault(require("./deploy"));
10
9
  const generate_1 = __importDefault(require("./generate"));
11
- // eslint-disable-next-line @typescript-eslint/no-var-requires
12
- const electron = require('electron');
13
10
  commander_1.program
14
11
  // eslint-disable-next-line @typescript-eslint/no-var-requires
15
12
  .version(require('../package').version)
16
13
  .usage('<command> [options]');
17
- // Auth command
18
- commander_1.program
19
- .command('auth')
20
- .description('Authenticate to dataverse')
21
- .action(() => {
22
- const child = child_process_1.spawn(electron, [`${__dirname}/.`], {
23
- stdio: 'inherit',
24
- windowsHide: false
25
- });
26
- child.on('close', code => {
27
- process.exit(code || undefined);
28
- });
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- ['SIGINT', 'SIGTERM'].forEach((signal) => {
31
- process.on(signal, () => {
32
- if (!child.killed) {
33
- child.kill(signal);
34
- }
35
- });
36
- });
37
- });
38
14
  // Deploy command
39
15
  commander_1.program
40
16
  .command('deploy')
@@ -48,7 +24,7 @@ commander_1.program
48
24
  commander_1.program
49
25
  .command('generate')
50
26
  .description('Generate early-bound TypeScript file for specified table')
51
- .argument('<table>', 'Table to generate')
27
+ .argument('[table]', 'Table to generate')
52
28
  .action((table) => {
53
29
  generate_1.default(table);
54
30
  });
package/lib/deploy.js CHANGED
@@ -10,7 +10,7 @@ const just_scripts_utils_1 = require("just-scripts-utils");
10
10
  const assemblyDeploy_1 = require("./assemblyDeploy");
11
11
  const webResourceDeploy_1 = require("./webResourceDeploy");
12
12
  const node_1 = require("dataverse-webapi/lib/node");
13
- const tokenCache_1 = require("./tokenCache");
13
+ const auth_1 = require("./auth");
14
14
  async function deploy(type, files) {
15
15
  if (!type || (type !== 'webresource' && type !== 'assembly')) {
16
16
  const invalid = type !== undefined && type !== 'webresource' && type !== 'assembly';
@@ -18,7 +18,7 @@ async function deploy(type, files) {
18
18
  const { typePrompt } = await prompts_1.default({
19
19
  type: 'select',
20
20
  name: 'typePrompt',
21
- message: `${invalidMessage} Select project type to deploy`,
21
+ message: `${invalidMessage} select project type to deploy`,
22
22
  choices: [
23
23
  { title: 'web resource', value: 'webresource' },
24
24
  { title: 'plugin or workflow activity', value: 'assembly' }
@@ -33,9 +33,16 @@ async function deploy(type, files) {
33
33
  return;
34
34
  }
35
35
  const creds = JSON.parse(credsFile).connection;
36
- const token = tokenCache_1.getTokenFromCache(creds.server);
37
- if (!token.accessToken) {
38
- just_scripts_utils_1.logger.error('use dataverse-utils auth command to get access token before deploying');
36
+ let token = null;
37
+ try {
38
+ token = await auth_1.getAccessToken(creds.tenant, creds.server);
39
+ }
40
+ catch (ex) {
41
+ just_scripts_utils_1.logger.error(`failed to acquire access token: ${ex.message}`);
42
+ return;
43
+ }
44
+ if (token == null || token.accessToken == null) {
45
+ just_scripts_utils_1.logger.error('failed to acquire access token');
39
46
  return;
40
47
  }
41
48
  const apiConfig = new node_1.WebApiConfig('8.2', token.accessToken, creds.server);
package/lib/generate.js CHANGED
@@ -3,13 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const prompts_1 = __importDefault(require("prompts"));
6
7
  const fs_1 = __importDefault(require("fs"));
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const just_scripts_utils_1 = require("just-scripts-utils");
9
10
  const dataverse_service_1 = require("./dataverse.service");
10
11
  const node_1 = require("dataverse-webapi/lib/node");
11
- const tokenCache_1 = require("./tokenCache");
12
+ const auth_1 = require("./auth");
12
13
  async function generate(table) {
14
+ if (!table) {
15
+ const { tablePrompt } = await prompts_1.default({
16
+ type: 'text',
17
+ name: 'tablePrompt',
18
+ message: `select table generate`
19
+ });
20
+ table = tablePrompt;
21
+ }
13
22
  const currentPath = '.';
14
23
  const credsFile = fs_1.default.readFileSync(path_1.default.resolve(currentPath, 'dataverse.config.json'), 'utf8');
15
24
  if (credsFile == null) {
@@ -17,9 +26,16 @@ async function generate(table) {
17
26
  return;
18
27
  }
19
28
  const creds = JSON.parse(credsFile).connection;
20
- const token = tokenCache_1.getTokenFromCache(creds.server);
21
- if (!token.accessToken) {
22
- just_scripts_utils_1.logger.error('use dataverse-utils auth command to get access token before deploying');
29
+ let token = null;
30
+ try {
31
+ token = await auth_1.getAccessToken(creds.tenant, creds.server);
32
+ }
33
+ catch (ex) {
34
+ just_scripts_utils_1.logger.error(`failed to acquire access token: ${ex.message}`);
35
+ return;
36
+ }
37
+ if (token == null || token.accessToken == null) {
38
+ just_scripts_utils_1.logger.error('failed to acquire access token');
23
39
  return;
24
40
  }
25
41
  const apiConfig = new node_1.WebApiConfig('8.2', token.accessToken, creds.server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataverse-utils",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -16,23 +16,21 @@
16
16
  "scripts": {
17
17
  "build": "tsc -p .",
18
18
  "lint": "eslint",
19
- "prebuild": "rimraf lib",
20
- "start": "electron ."
19
+ "prebuild": "rimraf lib"
21
20
  },
22
21
  "dependencies": {
23
- "adal-node": "^0.2.2",
24
22
  "envinfo": "^7.8.1",
23
+ "@azure/msal-node": "^1.3.1",
25
24
  "prompts": "^2.4.1",
26
25
  "dataverse-webapi": "^2.0.0",
27
- "commander": "^8.1.0",
26
+ "commander": "^8.2.0",
28
27
  "just-scripts-utils": "^1.1.4",
29
28
  "cryptr": "^6.0.2",
30
- "os": "^0.1.2",
31
- "electron": "^14.0.0"
29
+ "os": "^0.1.2"
32
30
  },
33
31
  "devDependencies": {
34
32
  "@types/cryptr": "^4.0.1",
35
- "@types/node": "^14.14.22",
33
+ "@types/node": "^16.9.1",
36
34
  "@types/prompts": "^2.0.14"
37
35
  }
38
36
  }
package/lib/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export default function getAccessToken(): Promise<void>;
package/lib/index.js DELETED
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const adal_node_1 = require("adal-node");
8
- const electron_1 = require("electron");
9
- const tokenCache_1 = require("./tokenCache");
10
- const fs_1 = __importDefault(require("fs"));
11
- const path_1 = __importDefault(require("path"));
12
- const just_scripts_utils_1 = require("just-scripts-utils");
13
- const clientId = '51f81489-12ee-4a9e-aaae-a2591f45987d';
14
- const authorityHostUrl = 'https://login.windows.net/common';
15
- const redirect = 'app://58145B91-0C36-4500-8554-080854F2AC97';
16
- function getTokenWithAuthCode(url, redirectUrl) {
17
- const regex = /(?<=code=)[^&]*/gm;
18
- const authCode = regex.exec(redirectUrl);
19
- if (!authCode) {
20
- throw new Error('Auth code not found in redirect');
21
- }
22
- return new Promise((resolve, reject) => {
23
- const context = new adal_node_1.AuthenticationContext(authorityHostUrl);
24
- context.acquireTokenWithAuthorizationCode(authCode[0], redirect, url, clientId, '', (err, tokenResponse) => {
25
- if (err) {
26
- reject(err);
27
- }
28
- else {
29
- resolve(tokenResponse);
30
- }
31
- });
32
- });
33
- }
34
- async function authenticate(url, tenant) {
35
- const authUrl = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`;
36
- await electron_1.app.whenReady();
37
- return new Promise((resolve, reject) => {
38
- let loginComplete = false;
39
- // Create the browser window.
40
- const win = new electron_1.BrowserWindow({
41
- width: 800,
42
- height: 600,
43
- autoHideMenuBar: true,
44
- titleBarStyle: 'hidden',
45
- webPreferences: {
46
- contextIsolation: true
47
- },
48
- });
49
- // Navigate to the get code page
50
- win.loadURL(`${authUrl}?client_id=${clientId}&response_type=code&redirect_uri=${encodeURIComponent(redirect)}&scope=openid`);
51
- win.on('closed', () => {
52
- if (!loginComplete) {
53
- reject('Login Closed');
54
- }
55
- });
56
- win.webContents.on('will-redirect', (event, newUrl) => {
57
- // Check if this is the success callback
58
- if (newUrl.toLowerCase().startsWith(redirect.toLowerCase())) {
59
- // Stop the redirect to the app: endpoint
60
- event.preventDefault();
61
- loginComplete = true;
62
- getTokenWithAuthCode(url, newUrl).then((tokenResponse) => {
63
- const token = tokenResponse;
64
- win.close();
65
- resolve(token);
66
- }, (err) => {
67
- console.log('Token Acquisition failed:' + JSON.stringify(err));
68
- reject(err);
69
- });
70
- }
71
- });
72
- });
73
- }
74
- async function getAccessToken() {
75
- const currentPath = '.';
76
- const credsFile = fs_1.default.readFileSync(path_1.default.resolve(currentPath, 'dataverse.config.json'), 'utf8');
77
- if (credsFile == null) {
78
- throw new Error('unable to find dataverse.config.json file');
79
- }
80
- const creds = JSON.parse(credsFile).connection;
81
- return new Promise((resolve, reject) => {
82
- const token = tokenCache_1.getTokenFromCache(creds.server);
83
- if (!token.expiresOn) {
84
- // Get initial token
85
- authenticate(creds.server, creds.tenant).then(authToken => {
86
- tokenCache_1.addTokenToCache(creds.server, authToken);
87
- resolve();
88
- }).catch((ex) => reject(ex));
89
- }
90
- else {
91
- const expiryDate = new Date(Date.parse(token.expiresOn.toString()));
92
- const nowDate = new Date();
93
- const expiresInMinutes = (expiryDate - nowDate) / 1000 / 60;
94
- const hasTokenExpired = expiresInMinutes < 5;
95
- if (hasTokenExpired) {
96
- const context = new adal_node_1.AuthenticationContext(authorityHostUrl);
97
- // Get new token using refresh token
98
- context.acquireTokenWithRefreshToken(token.refreshToken, clientId, creds.server, (err, tokenResponse) => {
99
- if (err) {
100
- tokenCache_1.addTokenToCache(creds.server, {});
101
- reject('Failed to acquire new token from refresh token. Token cache has been cleared and you must reauthenticate');
102
- }
103
- else {
104
- const newToken = tokenResponse;
105
- tokenCache_1.addTokenToCache(creds.server, newToken);
106
- resolve();
107
- }
108
- });
109
- }
110
- else {
111
- // Use current token
112
- resolve();
113
- }
114
- }
115
- });
116
- }
117
- exports.default = getAccessToken;
118
- async function main() {
119
- try {
120
- await getAccessToken();
121
- }
122
- catch (ex) {
123
- just_scripts_utils_1.logger.error(ex);
124
- }
125
- electron_1.app.exit();
126
- }
127
- main();
@@ -1,6 +0,0 @@
1
- import { TokenResponse } from 'adal-node';
2
- export interface TokenCache {
3
- [index: string]: string;
4
- }
5
- export declare function addTokenToCache(url: string, token: TokenResponse): void;
6
- export declare function getTokenFromCache(url: string): TokenResponse;
package/lib/tokenCache.js DELETED
@@ -1,40 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getTokenFromCache = exports.addTokenToCache = void 0;
7
- const cryptr_1 = __importDefault(require("cryptr"));
8
- const os_1 = __importDefault(require("os"));
9
- const path_1 = __importDefault(require("path"));
10
- const fs_1 = __importDefault(require("fs"));
11
- function getCachePath() {
12
- return path_1.default.join(os_1.default.homedir(), '.dataverse-deploy');
13
- }
14
- function getCache() {
15
- const path = getCachePath();
16
- if (fs_1.default.existsSync(path)) {
17
- const cache = fs_1.default.readFileSync(path);
18
- const crypt = new cryptr_1.default(os_1.default.userInfo.name);
19
- const decrypted = crypt.decrypt(cache.toString());
20
- return JSON.parse(decrypted);
21
- }
22
- return {};
23
- }
24
- function addTokenToCache(url, token) {
25
- const cache = getCache();
26
- cache[url] = JSON.stringify(token);
27
- const crypt = new cryptr_1.default(os_1.default.userInfo.name);
28
- const encrypted = crypt.encrypt(JSON.stringify(cache));
29
- fs_1.default.writeFileSync(getCachePath(), encrypted);
30
- }
31
- exports.addTokenToCache = addTokenToCache;
32
- function getTokenFromCache(url) {
33
- const cache = getCache();
34
- const urlToken = cache[url];
35
- if (!urlToken) {
36
- return {};
37
- }
38
- return JSON.parse(urlToken);
39
- }
40
- exports.getTokenFromCache = getTokenFromCache;