@forge/cli-shared 9.10.0-next.1 → 9.10.0-next.1-experimental-1e95696
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.md +17 -0
- package/out/apps/get-app-owner.d.ts.map +1 -1
- package/out/auth/personal/credential-store.d.ts +17 -23
- package/out/auth/personal/credential-store.d.ts.map +1 -1
- package/out/auth/personal/credential-store.js +196 -120
- package/out/auth/personal/token-authenticator.js +1 -1
- package/out/graphql/gql/graphql.d.ts +437 -8
- package/out/graphql/gql/graphql.d.ts.map +1 -1
- package/out/ui/text.d.ts +3 -12
- package/out/ui/text.d.ts.map +1 -1
- package/out/ui/text.js +5 -28
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @forge/cli-shared
|
|
2
2
|
|
|
3
|
+
## 9.10.0-next.1-experimental-1e95696
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- ffd952f: Replace Keytar with a dedicated credential file protected by macOS and Linux permissions or Windows ACLs. This is a breaking authentication change: users with credentials stored in the OS keychain must run `forge login` once after upgrading. `FORGE_EMAIL` and `FORGE_API_TOKEN` continue to take precedence over saved credentials.
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- e865360: Bump @forge/util with latest aris and dependencies
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [e865360]
|
|
16
|
+
- Updated dependencies [5d8a857]
|
|
17
|
+
- @forge/util@3.1.0-next.0-experimental-1e95696
|
|
18
|
+
- @forge/manifest@13.8.1-next.0-experimental-1e95696
|
|
19
|
+
|
|
3
20
|
## 9.10.0-next.1
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-app-owner.d.ts","sourceRoot":"","sources":["../../src/apps/get-app-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA4B,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,qBAAa,oBAAqB,SAAQ,KAAK;;CAI9C;AAED,eAAO,MAAM,aAAa;
|
|
1
|
+
{"version":3,"file":"get-app-owner.d.ts","sourceRoot":"","sources":["../../src/apps/get-app-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA4B,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,qBAAa,oBAAqB,SAAQ,KAAK;;CAI9C;AAED,eAAO,MAAM,aAAa;QAuC0tz4G,8CAAc;GA9Bhwz4G,CAAC;AAEH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBADZ,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,iBAAiB;IAGrC,OAAO;;;;;;;;;;;;;YAMN,WAAW;CAe1B"}
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import * as t from 'io-ts';
|
|
2
|
-
import { CachedConf } from '../../cache';
|
|
3
2
|
import { UserError } from '../../shared';
|
|
4
|
-
import { Logger } from '../../ui';
|
|
5
3
|
import { UserRepository } from './user-repository';
|
|
6
4
|
export declare const EMAIL_KEY = "FORGE_EMAIL";
|
|
7
5
|
export declare const API_TOKEN_KEY = "FORGE_API_TOKEN";
|
|
8
|
-
export interface Keytar {
|
|
9
|
-
getPassword(service: string, account: string): Promise<string | null>;
|
|
10
|
-
setPassword(service: string, account: string, password: string): Promise<void>;
|
|
11
|
-
deletePassword(service: string, account: string): Promise<void>;
|
|
12
|
-
}
|
|
13
6
|
export declare const personalApiCredentialsShape: t.TypeC<{
|
|
14
7
|
email: t.StringC;
|
|
15
8
|
token: t.StringC;
|
|
@@ -24,11 +17,9 @@ export type PersonalApiCredentialsValidated = t.TypeOf<typeof personalApiCredent
|
|
|
24
17
|
export declare class NoTokenInStoreError extends UserError {
|
|
25
18
|
constructor();
|
|
26
19
|
}
|
|
27
|
-
export declare class
|
|
20
|
+
export declare class CredentialStoreSecurityError extends UserError {
|
|
28
21
|
constructor();
|
|
29
22
|
}
|
|
30
|
-
export declare class KeytarAccessError extends UserError {
|
|
31
|
-
}
|
|
32
23
|
export interface CredentialGetter {
|
|
33
24
|
getCredentials(): Promise<PersonalApiCredentialsValidated>;
|
|
34
25
|
}
|
|
@@ -36,22 +27,25 @@ export interface CredentialStore extends CredentialGetter {
|
|
|
36
27
|
setCredentials(credentials: PersonalApiCredentialsValidated): Promise<void>;
|
|
37
28
|
deleteCredentials(): Promise<void>;
|
|
38
29
|
}
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
private readonly keytar;
|
|
43
|
-
private readonly cachedConfig;
|
|
30
|
+
export type CredentialStoreCommandRunner = (command: string, args: readonly string[]) => Promise<string>;
|
|
31
|
+
export declare function getCredentialsFilePath(currentPlatform?: NodeJS.Platform, environment?: NodeJS.ProcessEnv, userHome?: string): string;
|
|
32
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
44
33
|
private readonly userRepository;
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
private readonly account;
|
|
35
|
+
private readonly credentialsFilePath;
|
|
36
|
+
private readonly currentPlatform;
|
|
37
|
+
private readonly commandRunner;
|
|
38
|
+
private windowsUserSid;
|
|
39
|
+
constructor(userRepository: UserRepository, account: string, credentialsFilePath?: string, currentPlatform?: NodeJS.Platform, commandRunner?: CredentialStoreCommandRunner);
|
|
40
|
+
private getWindowsUserSid;
|
|
41
|
+
private applyWindowsAcl;
|
|
42
|
+
private secureWindowsStorage;
|
|
43
|
+
private securePosixStorage;
|
|
44
|
+
private secureStorage;
|
|
45
|
+
private readCredentialsFile;
|
|
52
46
|
getCredentials(): Promise<PersonalApiCredentialsValidated>;
|
|
53
47
|
setCredentials(credentials: PersonalApiCredentialsValidated): Promise<void>;
|
|
54
48
|
deleteCredentials(): Promise<void>;
|
|
55
49
|
}
|
|
56
|
-
export declare function getCredentialStore(
|
|
50
|
+
export declare function getCredentialStore(userRepository: UserRepository): CredentialStore;
|
|
57
51
|
//# sourceMappingURL=credential-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/auth/personal/credential-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/auth/personal/credential-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,CAAC,MAAM,OAAO,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,eAAO,MAAM,SAAS,gBAAgB,CAAC;AACvC,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAiB/C,eAAO,MAAM,2BAA2B;;;EAGtC,CAAC;AAEH,eAAO,MAAM,oCAAoC;;;;EAI/C,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAClF,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,oCAAoC,CAAC,CAAC;AAEpG,qBAAa,mBAAoB,SAAQ,SAAS;;CAIjD;AAED,qBAAa,4BAA6B,SAAQ,SAAS;;CAI1D;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,IAAI,OAAO,CAAC,+BAA+B,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,cAAc,CAAC,WAAW,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5E,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAID,MAAM,MAAM,4BAA4B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAezG,wBAAgB,sBAAsB,CACpC,eAAe,kBAAa,EAC5B,WAAW,GAAE,MAAM,CAAC,UAAwB,EAC5C,QAAQ,SAAY,GACnB,MAAM,CAwBR;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAIvD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAPhC,OAAO,CAAC,cAAc,CAA8B;gBAGjC,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,MAAM,EACf,mBAAmB,SAA2B,EAC9C,eAAe,kBAAa,EAC5B,aAAa,GAAE,4BAAwD;YAG5E,iBAAiB;YAWjB,eAAe;YAaf,oBAAoB;YA0BpB,kBAAkB;YAiDlB,aAAa;YAab,mBAAmB;IA4B3B,cAAc,IAAI,OAAO,CAAC,+BAA+B,CAAC;IAkB1D,cAAc,CAAC,WAAW,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3E,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;CAkBzC;AAED,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,cAAc,GAAG,eAAe,CAElF"}
|
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.FileCredentialStore = exports.CredentialStoreSecurityError = exports.NoTokenInStoreError = exports.personalApiCredentialsValidatedShape = exports.personalApiCredentialsShape = exports.API_TOKEN_KEY = exports.EMAIL_KEY = void 0;
|
|
4
|
+
exports.getCredentialsFilePath = getCredentialsFilePath;
|
|
4
5
|
exports.getCredentialStore = getCredentialStore;
|
|
5
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = require("fs");
|
|
6
9
|
const os_1 = require("os");
|
|
10
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
|
+
const util_1 = require("util");
|
|
7
12
|
const Either_1 = require("fp-ts/lib/Either");
|
|
8
13
|
const t = tslib_1.__importStar(require("io-ts"));
|
|
9
|
-
const cache_1 = require("../../cache");
|
|
10
|
-
const instruction_1 = require("../instruction");
|
|
11
14
|
const graphql_1 = require("../../graphql");
|
|
12
15
|
const shared_1 = require("../../shared");
|
|
13
16
|
const ui_1 = require("../../ui");
|
|
14
17
|
exports.EMAIL_KEY = 'FORGE_EMAIL';
|
|
15
18
|
exports.API_TOKEN_KEY = 'FORGE_API_TOKEN';
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
19
|
+
const CREDENTIALS_FILE_NAME = 'credentials.json';
|
|
20
|
+
const CREDENTIALS_DIRECTORY_MODE = 0o700;
|
|
21
|
+
const CREDENTIALS_FILE_MODE = 0o600;
|
|
22
|
+
const PERMISSION_BITS_MASK = 0o777;
|
|
23
|
+
const WINDOWS_SYSTEM_SID = 'S-1-5-18';
|
|
24
|
+
const WINDOWS_ADMINISTRATORS_SID = 'S-1-5-32-544';
|
|
25
|
+
var CredentialAccount;
|
|
26
|
+
(function (CredentialAccount) {
|
|
27
|
+
CredentialAccount["DEV"] = "Ecosystem-staging";
|
|
28
|
+
CredentialAccount["STG"] = "Ecosystem-staging";
|
|
29
|
+
CredentialAccount["PROD"] = "Ecosystem";
|
|
30
|
+
CredentialAccount["FEDRAMP_MODERATE_STAGING"] = "Ecosystem-fedm-staging";
|
|
31
|
+
CredentialAccount["FEDRAMP_MODERATE_PROD"] = "Ecosystem-fedm-prod";
|
|
32
|
+
})(CredentialAccount || (CredentialAccount = {}));
|
|
34
33
|
exports.personalApiCredentialsShape = t.type({
|
|
35
34
|
email: t.string,
|
|
36
35
|
token: t.string
|
|
@@ -46,146 +45,223 @@ class NoTokenInStoreError extends shared_1.UserError {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
exports.NoTokenInStoreError = NoTokenInStoreError;
|
|
49
|
-
class
|
|
48
|
+
class CredentialStoreSecurityError extends shared_1.UserError {
|
|
50
49
|
constructor() {
|
|
51
|
-
super(ui_1.Text.error.
|
|
50
|
+
super(ui_1.Text.error.credentialsFilePermissions);
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
|
-
exports.
|
|
55
|
-
|
|
53
|
+
exports.CredentialStoreSecurityError = CredentialStoreSecurityError;
|
|
54
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
55
|
+
const runCredentialStoreCommand = async (command, args) => {
|
|
56
|
+
const { stdout } = await execFileAsync(command, [...args], { encoding: 'utf8', windowsHide: true });
|
|
57
|
+
return stdout;
|
|
58
|
+
};
|
|
59
|
+
const credentialsByAccountShape = t.record(t.string, exports.personalApiCredentialsValidatedShape);
|
|
60
|
+
function isFileNotFoundError(error) {
|
|
61
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
function getCredentialsFilePath(currentPlatform = (0, os_1.platform)(), environment = process.env, userHome = (0, os_1.homedir)()) {
|
|
64
|
+
const platformPath = currentPlatform === 'win32' ? path_1.default.win32 : path_1.default.posix;
|
|
65
|
+
let credentialsDirectory;
|
|
66
|
+
switch (currentPlatform) {
|
|
67
|
+
case 'darwin':
|
|
68
|
+
credentialsDirectory = platformPath.join(userHome, 'Library', 'Application Support', 'Atlassian', 'Forge');
|
|
69
|
+
break;
|
|
70
|
+
case 'win32':
|
|
71
|
+
credentialsDirectory = platformPath.join(environment.LOCALAPPDATA || platformPath.join(userHome, 'AppData', 'Local'), 'Atlassian', 'Forge');
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
credentialsDirectory = platformPath.join(environment.XDG_DATA_HOME || platformPath.join(userHome, '.local', 'share'), 'Atlassian', 'Forge');
|
|
75
|
+
}
|
|
76
|
+
return platformPath.join(credentialsDirectory, CREDENTIALS_FILE_NAME);
|
|
77
|
+
}
|
|
78
|
+
class FileCredentialStore {
|
|
63
79
|
userRepository;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
account;
|
|
81
|
+
credentialsFilePath;
|
|
82
|
+
currentPlatform;
|
|
83
|
+
commandRunner;
|
|
84
|
+
windowsUserSid;
|
|
85
|
+
constructor(userRepository, account, credentialsFilePath = getCredentialsFilePath(), currentPlatform = (0, os_1.platform)(), commandRunner = runCredentialStoreCommand) {
|
|
70
86
|
this.userRepository = userRepository;
|
|
71
|
-
this.
|
|
87
|
+
this.account = account;
|
|
88
|
+
this.credentialsFilePath = credentialsFilePath;
|
|
89
|
+
this.currentPlatform = currentPlatform;
|
|
90
|
+
this.commandRunner = commandRunner;
|
|
91
|
+
}
|
|
92
|
+
async getWindowsUserSid() {
|
|
93
|
+
this.windowsUserSid ??= this.commandRunner('whoami.exe', ['/user', '/fo', 'csv', '/nh']).then((output) => {
|
|
94
|
+
const sid = output.trim().match(/"?(S-\d(?:-\d+)+)"?$/)?.[1];
|
|
95
|
+
if (!sid) {
|
|
96
|
+
throw new Error('Could not determine the current Windows user SID');
|
|
97
|
+
}
|
|
98
|
+
return sid;
|
|
99
|
+
});
|
|
100
|
+
return this.windowsUserSid;
|
|
72
101
|
}
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
case 'win32':
|
|
85
|
-
error = ui_1.Text.error.keytarAccessError.windows(message);
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
error = ui_1.Text.error.keytarAccessError.other(message);
|
|
89
|
-
}
|
|
90
|
-
throw new KeytarAccessError(error);
|
|
102
|
+
async applyWindowsAcl(targetPath, directory) {
|
|
103
|
+
const userSid = await this.getWindowsUserSid();
|
|
104
|
+
const permission = directory ? '(OI)(CI)F' : 'F';
|
|
105
|
+
await this.commandRunner('icacls.exe', [
|
|
106
|
+
targetPath,
|
|
107
|
+
'/inheritance:r',
|
|
108
|
+
'/grant:r',
|
|
109
|
+
`*${userSid}:${permission}`,
|
|
110
|
+
`*${WINDOWS_SYSTEM_SID}:${permission}`,
|
|
111
|
+
`*${WINDOWS_ADMINISTRATORS_SID}:${permission}`
|
|
112
|
+
]);
|
|
91
113
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
throw new NoTokenInStoreError();
|
|
95
|
-
}
|
|
96
|
-
let credentials;
|
|
114
|
+
async secureWindowsStorage(createFile) {
|
|
115
|
+
let fileExists = true;
|
|
97
116
|
try {
|
|
98
|
-
|
|
117
|
+
await fs_1.promises.access(this.credentialsFilePath);
|
|
99
118
|
}
|
|
100
|
-
catch {
|
|
101
|
-
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (!isFileNotFoundError(error)) {
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
fileExists = false;
|
|
102
124
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
throw new NoTokenInStoreError();
|
|
125
|
+
if (!fileExists && !createFile) {
|
|
126
|
+
return;
|
|
106
127
|
}
|
|
107
|
-
|
|
128
|
+
const credentialsDirectory = path_1.default.dirname(this.credentialsFilePath);
|
|
129
|
+
await fs_1.promises.mkdir(credentialsDirectory, { recursive: true });
|
|
130
|
+
await this.applyWindowsAcl(credentialsDirectory, true);
|
|
131
|
+
if (!fileExists) {
|
|
132
|
+
await fs_1.promises.writeFile(this.credentialsFilePath, '', { flag: 'wx' });
|
|
133
|
+
}
|
|
134
|
+
await this.applyWindowsAcl(this.credentialsFilePath, false);
|
|
108
135
|
}
|
|
109
|
-
async
|
|
110
|
-
let
|
|
136
|
+
async securePosixStorage(createFile) {
|
|
137
|
+
let fileExists = true;
|
|
111
138
|
try {
|
|
112
|
-
|
|
139
|
+
await fs_1.promises.access(this.credentialsFilePath);
|
|
113
140
|
}
|
|
114
|
-
catch (
|
|
115
|
-
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!isFileNotFoundError(error)) {
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
fileExists = false;
|
|
116
146
|
}
|
|
117
|
-
if (!
|
|
118
|
-
return
|
|
147
|
+
if (!fileExists && !createFile) {
|
|
148
|
+
return;
|
|
119
149
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
const credentialsDirectory = path_1.default.dirname(this.credentialsFilePath);
|
|
151
|
+
await fs_1.promises.mkdir(credentialsDirectory, {
|
|
152
|
+
recursive: true,
|
|
153
|
+
mode: CREDENTIALS_DIRECTORY_MODE
|
|
154
|
+
});
|
|
155
|
+
await fs_1.promises.chmod(credentialsDirectory, CREDENTIALS_DIRECTORY_MODE);
|
|
156
|
+
const directoryStat = await fs_1.promises.stat(credentialsDirectory);
|
|
157
|
+
const currentUserId = process.getuid?.();
|
|
158
|
+
if (currentUserId === undefined ||
|
|
159
|
+
!directoryStat.isDirectory() ||
|
|
160
|
+
directoryStat.uid !== currentUserId ||
|
|
161
|
+
(directoryStat.mode & PERMISSION_BITS_MASK) !== CREDENTIALS_DIRECTORY_MODE) {
|
|
162
|
+
throw new Error('The credentials directory is not owned exclusively by the current user');
|
|
163
|
+
}
|
|
164
|
+
if (!fileExists) {
|
|
165
|
+
const file = await fs_1.promises.open(this.credentialsFilePath, 'wx', CREDENTIALS_FILE_MODE);
|
|
166
|
+
await file.close();
|
|
167
|
+
}
|
|
168
|
+
await fs_1.promises.chmod(this.credentialsFilePath, CREDENTIALS_FILE_MODE);
|
|
169
|
+
const fileStat = await fs_1.promises.stat(this.credentialsFilePath);
|
|
170
|
+
if (!fileStat.isFile() ||
|
|
171
|
+
fileStat.uid !== currentUserId ||
|
|
172
|
+
(fileStat.mode & PERMISSION_BITS_MASK) !== CREDENTIALS_FILE_MODE) {
|
|
173
|
+
throw new Error('Could not restrict access to the credentials file');
|
|
127
174
|
}
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
deleteInsecurePlaintextCredentials() {
|
|
131
|
-
this.cachedConfig.delete(CACHE_CREDENTIALS_KEY);
|
|
132
175
|
}
|
|
133
|
-
async
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (this.keytar) {
|
|
138
|
-
await this.setCredentials(plaintextCredentials);
|
|
139
|
-
this.logger.warn(ui_1.Text.warning.plaintextCredentialsMigrated);
|
|
176
|
+
async secureStorage(createFile) {
|
|
177
|
+
try {
|
|
178
|
+
if (this.currentPlatform === 'win32') {
|
|
179
|
+
await this.secureWindowsStorage(createFile);
|
|
140
180
|
}
|
|
141
181
|
else {
|
|
142
|
-
this.
|
|
182
|
+
await this.securePosixStorage(createFile);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
await fs_1.promises.rm(this.credentialsFilePath, { force: true }).catch(() => undefined);
|
|
187
|
+
throw new CredentialStoreSecurityError();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async readCredentialsFile() {
|
|
191
|
+
let contents;
|
|
192
|
+
try {
|
|
193
|
+
await this.secureStorage(false);
|
|
194
|
+
contents = await fs_1.promises.readFile(this.credentialsFilePath, 'utf8');
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (isFileNotFoundError(error)) {
|
|
198
|
+
return {};
|
|
143
199
|
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
let parsed;
|
|
203
|
+
try {
|
|
204
|
+
parsed = JSON.parse(contents);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
throw new NoTokenInStoreError();
|
|
208
|
+
}
|
|
209
|
+
const decodeResult = credentialsByAccountShape.decode(parsed);
|
|
210
|
+
if ((0, Either_1.isLeft)(decodeResult)) {
|
|
211
|
+
throw new NoTokenInStoreError();
|
|
144
212
|
}
|
|
213
|
+
return decodeResult.right;
|
|
214
|
+
}
|
|
215
|
+
async getCredentials() {
|
|
145
216
|
if (process.env[exports.API_TOKEN_KEY] && process.env[exports.EMAIL_KEY]) {
|
|
146
217
|
const credentials = {
|
|
147
218
|
email: process.env[exports.EMAIL_KEY],
|
|
148
219
|
token: process.env[exports.API_TOKEN_KEY]
|
|
149
220
|
};
|
|
150
221
|
const { accountId } = await this.userRepository.getUser(credentials);
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else if (this.keytar) {
|
|
154
|
-
result = await this.getCredentialsKeytar(this.keytar);
|
|
222
|
+
return { ...credentials, accountId };
|
|
155
223
|
}
|
|
156
|
-
|
|
157
|
-
|
|
224
|
+
const credentials = (await this.readCredentialsFile())[this.account];
|
|
225
|
+
if (!credentials) {
|
|
226
|
+
throw new NoTokenInStoreError();
|
|
158
227
|
}
|
|
159
|
-
|
|
228
|
+
return credentials;
|
|
160
229
|
}
|
|
161
230
|
async setCredentials(credentials) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
catch (e) {
|
|
167
|
-
throw new NoKeytarError();
|
|
168
|
-
}
|
|
231
|
+
let credentialsByAccount;
|
|
232
|
+
try {
|
|
233
|
+
credentialsByAccount = await this.readCredentialsFile();
|
|
169
234
|
}
|
|
170
|
-
|
|
171
|
-
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (!(error instanceof NoTokenInStoreError)) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
credentialsByAccount = {};
|
|
172
240
|
}
|
|
241
|
+
credentialsByAccount[this.account] = credentials;
|
|
242
|
+
await this.secureStorage(true);
|
|
243
|
+
await fs_1.promises.writeFile(this.credentialsFilePath, `${JSON.stringify(credentialsByAccount, null, 2)}\n`, 'utf8');
|
|
244
|
+
await this.secureStorage(false);
|
|
173
245
|
}
|
|
174
246
|
async deleteCredentials() {
|
|
175
|
-
|
|
247
|
+
const credentialsByAccount = await this.readCredentialsFile();
|
|
248
|
+
delete credentialsByAccount[this.account];
|
|
249
|
+
if (Object.keys(credentialsByAccount).length === 0) {
|
|
176
250
|
try {
|
|
177
|
-
await
|
|
251
|
+
await fs_1.promises.unlink(this.credentialsFilePath);
|
|
178
252
|
}
|
|
179
|
-
catch (
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (!isFileNotFoundError(error)) {
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
180
257
|
}
|
|
258
|
+
return;
|
|
181
259
|
}
|
|
182
|
-
this.
|
|
260
|
+
await fs_1.promises.writeFile(this.credentialsFilePath, `${JSON.stringify(credentialsByAccount, null, 2)}\n`, 'utf8');
|
|
261
|
+
await this.secureStorage(false);
|
|
183
262
|
}
|
|
184
263
|
}
|
|
185
|
-
exports.
|
|
186
|
-
function getCredentialStore(
|
|
187
|
-
|
|
188
|
-
const cachedConfig = cache_1.CachedConf.getCache(cache_1.CONFIG_PROJECT_NAME);
|
|
189
|
-
const instructionsUrl = (0, instruction_1.getInstructionsUrl)();
|
|
190
|
-
return new CredentialStoreImpl(logger, instructionsUrl, keytar, cachedConfig, userRepository);
|
|
264
|
+
exports.FileCredentialStore = FileCredentialStore;
|
|
265
|
+
function getCredentialStore(userRepository) {
|
|
266
|
+
return new FileCredentialStore(userRepository, (0, graphql_1.getEnvironmentConfig)(CredentialAccount));
|
|
191
267
|
}
|
|
@@ -19,7 +19,7 @@ class PersonalTokenAuthenticator {
|
|
|
19
19
|
if (process.env[credential_store_1.API_TOKEN_KEY] && process.env[credential_store_1.EMAIL_KEY]) {
|
|
20
20
|
return ui_1.Text.error.invalidTokenInEnv(instructionsUrl, credential_store_1.EMAIL_KEY, credential_store_1.API_TOKEN_KEY);
|
|
21
21
|
}
|
|
22
|
-
return ui_1.Text.error.
|
|
22
|
+
return ui_1.Text.error.invalidTokenInCredentialFile;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
exports.PersonalTokenAuthenticator = PersonalTokenAuthenticator;
|