@forge/cli-shared 9.10.0-next.1 → 9.10.0-next.1-experimental-65945d2

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @forge/cli-shared
2
2
 
3
+ ## 9.10.0-next.1-experimental-65945d2
4
+
5
+ ### Minor Changes
6
+
7
+ - e865360: Bump @forge/util with latest aris and dependencies
8
+
9
+ ### Patch Changes
10
+
11
+ - ffd952f: Add an experimental file-backed credential store with restricted macOS, Linux, and Windows permissions.
12
+ - Updated dependencies [e865360]
13
+ - Updated dependencies [5d8a857]
14
+ - @forge/util@3.1.0-next.0-experimental-65945d2
15
+ - @forge/manifest@13.8.1-next.0-experimental-65945d2
16
+
3
17
  ## 9.10.0-next.1
4
18
 
5
19
  ### 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;QAuC+sx2G,8CAAc;GA9Brvx2G,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
+ {"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;QAuC83n3G,8CAAc;GA9Bp6n3G,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"}
@@ -5,6 +5,7 @@ import { Logger } from '../../ui';
5
5
  import { UserRepository } from './user-repository';
6
6
  export declare const EMAIL_KEY = "FORGE_EMAIL";
7
7
  export declare const API_TOKEN_KEY = "FORGE_API_TOKEN";
8
+ export declare const FILE_CREDENTIAL_STORE_FLAG = "FORGE_CLI_ENABLE_FILE_CREDENTIAL_STORE";
8
9
  export interface Keytar {
9
10
  getPassword(service: string, account: string): Promise<string | null>;
10
11
  setPassword(service: string, account: string, password: string): Promise<void>;
@@ -29,6 +30,9 @@ export declare class NoKeytarError extends UserError {
29
30
  }
30
31
  export declare class KeytarAccessError extends UserError {
31
32
  }
33
+ export declare class CredentialStoreSecurityError extends UserError {
34
+ constructor();
35
+ }
32
36
  export interface CredentialGetter {
33
37
  getCredentials(): Promise<PersonalApiCredentialsValidated>;
34
38
  }
@@ -36,6 +40,26 @@ export interface CredentialStore extends CredentialGetter {
36
40
  setCredentials(credentials: PersonalApiCredentialsValidated): Promise<void>;
37
41
  deleteCredentials(): Promise<void>;
38
42
  }
43
+ export type CredentialStoreCommandRunner = (command: string, args: readonly string[]) => Promise<string>;
44
+ export declare function getCredentialsFilePath(currentPlatform?: NodeJS.Platform, environment?: NodeJS.ProcessEnv, userHome?: string): string;
45
+ export declare class FileCredentialStore implements CredentialStore {
46
+ private readonly userRepository;
47
+ private readonly account;
48
+ private readonly credentialsFilePath;
49
+ private readonly currentPlatform;
50
+ private readonly commandRunner;
51
+ private windowsUserSid;
52
+ constructor(userRepository: UserRepository, account: string, credentialsFilePath?: string, currentPlatform?: NodeJS.Platform, commandRunner?: CredentialStoreCommandRunner);
53
+ private getWindowsUserSid;
54
+ private applyWindowsAcl;
55
+ private secureWindowsStorage;
56
+ private securePosixStorage;
57
+ private secureStorage;
58
+ private readCredentialsFile;
59
+ getCredentials(): Promise<PersonalApiCredentialsValidated>;
60
+ setCredentials(credentials: PersonalApiCredentialsValidated): Promise<void>;
61
+ deleteCredentials(): Promise<void>;
62
+ }
39
63
  export declare class CredentialStoreImpl implements CredentialStore {
40
64
  private readonly logger;
41
65
  private readonly instructionsURL;
@@ -1 +1 @@
1
- {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/auth/personal/credential-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,OAAO,CAAC;AAC3B,OAAO,EAAuB,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAAE,SAAS,EAAa,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,MAAM,EAAQ,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,eAAO,MAAM,SAAS,gBAAgB,CAAC;AACvC,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAa/C,MAAM,WAAW,MAAM;IACrB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEtE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/E,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAUD,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,aAAc,SAAQ,SAAS;;CAI3C;AAED,qBAAa,iBAAkB,SAAQ,SAAS;CAAG;AAEnD,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;AAED,qBAAa,mBAAoB,YAAW,eAAe;IAIvD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,aAAa,CAAS;gBAGX,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,YAAY,EAAE,UAAU,EACxB,cAAc,EAAE,cAAc;IAKjD,OAAO,CAAC,eAAe;IAoBvB,iBAAiB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,+BAA+B;IAoB9E,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,+BAA+B,GAAG,SAAS,CAAC;IAehG,+BAA+B,IAAI,+BAA+B,GAAG,SAAS;IAS9E,kCAAkC,IAAI,IAAI;IAIpC,cAAc,IAAI,OAAO,CAAC,+BAA+B,CAAC;IAgC1D,cAAc,CAAC,WAAW,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3E,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;CAWzC;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,GAAG,eAAe,CAKlG"}
1
+ {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/auth/personal/credential-store.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CredentialStoreImpl = exports.KeytarAccessError = exports.NoKeytarError = exports.NoTokenInStoreError = exports.personalApiCredentialsValidatedShape = exports.personalApiCredentialsShape = exports.API_TOKEN_KEY = exports.EMAIL_KEY = void 0;
3
+ exports.CredentialStoreImpl = exports.FileCredentialStore = exports.CredentialStoreSecurityError = exports.KeytarAccessError = exports.NoKeytarError = exports.NoTokenInStoreError = exports.personalApiCredentialsValidatedShape = exports.personalApiCredentialsShape = exports.FILE_CREDENTIAL_STORE_FLAG = 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
14
  const cache_1 = require("../../cache");
@@ -13,8 +18,17 @@ const shared_1 = require("../../shared");
13
18
  const ui_1 = require("../../ui");
14
19
  exports.EMAIL_KEY = 'FORGE_EMAIL';
15
20
  exports.API_TOKEN_KEY = 'FORGE_API_TOKEN';
21
+ exports.FILE_CREDENTIAL_STORE_FLAG = 'FORGE_CLI_ENABLE_FILE_CREDENTIAL_STORE';
16
22
  const KEYTAR_SERVICE = 'Atlassian';
17
23
  const CACHE_CREDENTIALS_KEY = 'forge-credentials';
24
+ const CREDENTIALS_FILE_NAME = 'credentials.json';
25
+ const CONFIG_DIRECTORY_SUFFIX = 'nodejs';
26
+ const CREDENTIALS_DIRECTORY_MODE = 0o700;
27
+ const CREDENTIALS_FILE_MODE = 0o600;
28
+ const PERMISSION_BITS_MASK = 0o777;
29
+ const GROUP_OR_OTHER_WRITE_BITS_MASK = 0o022;
30
+ const WINDOWS_SYSTEM_SID = 'S-1-5-18';
31
+ const WINDOWS_ADMINISTRATORS_SID = 'S-1-5-32-544';
18
32
  var KeytarAccount;
19
33
  (function (KeytarAccount) {
20
34
  KeytarAccount["DEV"] = "Ecosystem-staging";
@@ -55,6 +69,225 @@ exports.NoKeytarError = NoKeytarError;
55
69
  class KeytarAccessError extends shared_1.UserError {
56
70
  }
57
71
  exports.KeytarAccessError = KeytarAccessError;
72
+ class CredentialStoreSecurityError extends shared_1.UserError {
73
+ constructor() {
74
+ super(ui_1.Text.error.credentialsFilePermissions);
75
+ }
76
+ }
77
+ exports.CredentialStoreSecurityError = CredentialStoreSecurityError;
78
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
79
+ const runCredentialStoreCommand = async (command, args) => {
80
+ const { stdout } = await execFileAsync(command, [...args], { encoding: 'utf8', windowsHide: true });
81
+ return stdout;
82
+ };
83
+ const credentialsByAccountShape = t.record(t.string, exports.personalApiCredentialsValidatedShape);
84
+ function isFileNotFoundError(error) {
85
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
86
+ }
87
+ function getCredentialsFilePath(currentPlatform = (0, os_1.platform)(), environment = process.env, userHome = (0, os_1.homedir)()) {
88
+ const platformPath = currentPlatform === 'win32' ? path_1.default.win32 : path_1.default.posix;
89
+ let credentialsDirectory;
90
+ switch (currentPlatform) {
91
+ case 'darwin':
92
+ credentialsDirectory = platformPath.join(userHome, 'Library', 'Preferences', `${cache_1.CONFIG_PROJECT_NAME}-${CONFIG_DIRECTORY_SUFFIX}`);
93
+ break;
94
+ case 'win32':
95
+ credentialsDirectory = platformPath.join(environment.LOCALAPPDATA || platformPath.join(userHome, 'AppData', 'Local'), 'Atlassian', 'Forge');
96
+ break;
97
+ default:
98
+ credentialsDirectory = platformPath.join(environment.XDG_CONFIG_HOME || platformPath.join(userHome, '.config'), `${cache_1.CONFIG_PROJECT_NAME}-${CONFIG_DIRECTORY_SUFFIX}`);
99
+ }
100
+ return platformPath.join(credentialsDirectory, CREDENTIALS_FILE_NAME);
101
+ }
102
+ class FileCredentialStore {
103
+ userRepository;
104
+ account;
105
+ credentialsFilePath;
106
+ currentPlatform;
107
+ commandRunner;
108
+ windowsUserSid;
109
+ constructor(userRepository, account, credentialsFilePath = getCredentialsFilePath(), currentPlatform = (0, os_1.platform)(), commandRunner = runCredentialStoreCommand) {
110
+ this.userRepository = userRepository;
111
+ this.account = account;
112
+ this.credentialsFilePath = credentialsFilePath;
113
+ this.currentPlatform = currentPlatform;
114
+ this.commandRunner = commandRunner;
115
+ }
116
+ async getWindowsUserSid() {
117
+ this.windowsUserSid ??= this.commandRunner('whoami.exe', ['/user', '/fo', 'csv', '/nh']).then((output) => {
118
+ const sid = output.trim().match(/"?(S-\d(?:-\d+)+)"?$/)?.[1];
119
+ if (!sid) {
120
+ throw new Error('Could not determine the current Windows user SID');
121
+ }
122
+ return sid;
123
+ });
124
+ return this.windowsUserSid;
125
+ }
126
+ async applyWindowsAcl(targetPath, directory) {
127
+ const userSid = await this.getWindowsUserSid();
128
+ const permission = directory ? '(OI)(CI)F' : 'F';
129
+ await this.commandRunner('icacls.exe', [
130
+ targetPath,
131
+ '/inheritance:r',
132
+ '/grant:r',
133
+ `*${userSid}:${permission}`,
134
+ `*${WINDOWS_SYSTEM_SID}:${permission}`,
135
+ `*${WINDOWS_ADMINISTRATORS_SID}:${permission}`
136
+ ]);
137
+ }
138
+ async secureWindowsStorage(createFile) {
139
+ let fileExists = true;
140
+ try {
141
+ await fs_1.promises.access(this.credentialsFilePath);
142
+ }
143
+ catch (error) {
144
+ if (!isFileNotFoundError(error)) {
145
+ throw error;
146
+ }
147
+ fileExists = false;
148
+ }
149
+ if (!fileExists && !createFile) {
150
+ return;
151
+ }
152
+ const credentialsDirectory = path_1.default.dirname(this.credentialsFilePath);
153
+ await fs_1.promises.mkdir(credentialsDirectory, { recursive: true });
154
+ await this.applyWindowsAcl(credentialsDirectory, true);
155
+ if (!fileExists) {
156
+ await fs_1.promises.writeFile(this.credentialsFilePath, '', { flag: 'wx' });
157
+ }
158
+ await this.applyWindowsAcl(this.credentialsFilePath, false);
159
+ }
160
+ async securePosixStorage(createFile) {
161
+ let fileExists = true;
162
+ try {
163
+ await fs_1.promises.access(this.credentialsFilePath);
164
+ }
165
+ catch (error) {
166
+ if (!isFileNotFoundError(error)) {
167
+ throw error;
168
+ }
169
+ fileExists = false;
170
+ }
171
+ if (!fileExists && !createFile) {
172
+ return;
173
+ }
174
+ const credentialsDirectory = path_1.default.dirname(this.credentialsFilePath);
175
+ const createdDirectory = await fs_1.promises.mkdir(credentialsDirectory, {
176
+ recursive: true,
177
+ mode: CREDENTIALS_DIRECTORY_MODE
178
+ });
179
+ if (createdDirectory !== undefined) {
180
+ await fs_1.promises.chmod(credentialsDirectory, CREDENTIALS_DIRECTORY_MODE);
181
+ }
182
+ const directoryStat = await fs_1.promises.stat(credentialsDirectory);
183
+ const currentUserId = process.getuid?.();
184
+ if (currentUserId === undefined ||
185
+ !directoryStat.isDirectory() ||
186
+ directoryStat.uid !== currentUserId ||
187
+ (directoryStat.mode & GROUP_OR_OTHER_WRITE_BITS_MASK) !== 0) {
188
+ throw new Error('The credentials directory is not owned exclusively by the current user');
189
+ }
190
+ if (!fileExists) {
191
+ const file = await fs_1.promises.open(this.credentialsFilePath, 'wx', CREDENTIALS_FILE_MODE);
192
+ await file.close();
193
+ }
194
+ await fs_1.promises.chmod(this.credentialsFilePath, CREDENTIALS_FILE_MODE);
195
+ const fileStat = await fs_1.promises.stat(this.credentialsFilePath);
196
+ if (!fileStat.isFile() ||
197
+ fileStat.uid !== currentUserId ||
198
+ (fileStat.mode & PERMISSION_BITS_MASK) !== CREDENTIALS_FILE_MODE) {
199
+ throw new Error('Could not restrict access to the credentials file');
200
+ }
201
+ }
202
+ async secureStorage(createFile) {
203
+ try {
204
+ if (this.currentPlatform === 'win32') {
205
+ await this.secureWindowsStorage(createFile);
206
+ }
207
+ else {
208
+ await this.securePosixStorage(createFile);
209
+ }
210
+ }
211
+ catch {
212
+ await fs_1.promises.rm(this.credentialsFilePath, { force: true }).catch(() => undefined);
213
+ throw new CredentialStoreSecurityError();
214
+ }
215
+ }
216
+ async readCredentialsFile() {
217
+ let contents;
218
+ try {
219
+ await this.secureStorage(false);
220
+ contents = await fs_1.promises.readFile(this.credentialsFilePath, 'utf8');
221
+ }
222
+ catch (error) {
223
+ if (isFileNotFoundError(error)) {
224
+ return {};
225
+ }
226
+ throw error;
227
+ }
228
+ let parsed;
229
+ try {
230
+ parsed = JSON.parse(contents);
231
+ }
232
+ catch {
233
+ throw new NoTokenInStoreError();
234
+ }
235
+ const decodeResult = credentialsByAccountShape.decode(parsed);
236
+ if ((0, Either_1.isLeft)(decodeResult)) {
237
+ throw new NoTokenInStoreError();
238
+ }
239
+ return decodeResult.right;
240
+ }
241
+ async getCredentials() {
242
+ if (process.env[exports.API_TOKEN_KEY] && process.env[exports.EMAIL_KEY]) {
243
+ const credentials = {
244
+ email: process.env[exports.EMAIL_KEY],
245
+ token: process.env[exports.API_TOKEN_KEY]
246
+ };
247
+ const { accountId } = await this.userRepository.getUser(credentials);
248
+ return { ...credentials, accountId };
249
+ }
250
+ const credentials = (await this.readCredentialsFile())[this.account];
251
+ if (!credentials) {
252
+ throw new NoTokenInStoreError();
253
+ }
254
+ return credentials;
255
+ }
256
+ async setCredentials(credentials) {
257
+ let credentialsByAccount;
258
+ try {
259
+ credentialsByAccount = await this.readCredentialsFile();
260
+ }
261
+ catch (error) {
262
+ if (!(error instanceof NoTokenInStoreError)) {
263
+ throw error;
264
+ }
265
+ credentialsByAccount = {};
266
+ }
267
+ credentialsByAccount[this.account] = credentials;
268
+ await this.secureStorage(true);
269
+ await fs_1.promises.writeFile(this.credentialsFilePath, `${JSON.stringify(credentialsByAccount, null, 2)}\n`, 'utf8');
270
+ await this.secureStorage(false);
271
+ }
272
+ async deleteCredentials() {
273
+ const credentialsByAccount = await this.readCredentialsFile();
274
+ delete credentialsByAccount[this.account];
275
+ if (Object.keys(credentialsByAccount).length === 0) {
276
+ try {
277
+ await fs_1.promises.unlink(this.credentialsFilePath);
278
+ }
279
+ catch (error) {
280
+ if (!isFileNotFoundError(error)) {
281
+ throw error;
282
+ }
283
+ }
284
+ return;
285
+ }
286
+ await fs_1.promises.writeFile(this.credentialsFilePath, `${JSON.stringify(credentialsByAccount, null, 2)}\n`, 'utf8');
287
+ await this.secureStorage(false);
288
+ }
289
+ }
290
+ exports.FileCredentialStore = FileCredentialStore;
58
291
  class CredentialStoreImpl {
59
292
  logger;
60
293
  instructionsURL;
@@ -184,6 +417,10 @@ class CredentialStoreImpl {
184
417
  }
185
418
  exports.CredentialStoreImpl = CredentialStoreImpl;
186
419
  function getCredentialStore(logger, userRepository) {
420
+ const credentialAccount = (0, graphql_1.getEnvironmentConfig)(KeytarAccount);
421
+ if (process.env[exports.FILE_CREDENTIAL_STORE_FLAG] === 'true') {
422
+ return new FileCredentialStore(userRepository, credentialAccount);
423
+ }
187
424
  const keytar = getKeytar();
188
425
  const cachedConfig = cache_1.CachedConf.getCache(cache_1.CONFIG_PROJECT_NAME);
189
426
  const instructionsUrl = (0, instruction_1.getInstructionsUrl)();