@herodevs/cli 2.0.0-beta.17 → 2.0.0-beta.18

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/README.md CHANGED
@@ -38,6 +38,18 @@ npx @herodevs/cli@beta
38
38
  npm install -g @herodevs/cli@beta
39
39
  ```
40
40
 
41
+ #### Binary Installation
42
+
43
+ HeroDevs CLI is available as a binary installation, without requiring `npm`. To do that, you may either download and run the script manually, or use the following cURL or Wget command:
44
+
45
+ ```sh
46
+ curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.18/scripts/install.sh | bash
47
+ ```
48
+
49
+ ```sh
50
+ wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.18/scripts/install.sh | bash
51
+ ```
52
+
41
53
  ## Scanning Behavior
42
54
 
43
55
  The CLI is designed to be non-invasive:
@@ -1,5 +1,2 @@
1
1
  export declare function getRealmUrl(): string;
2
2
  export declare function getClientId(): string;
3
- export declare function getTokenServiceName(): string;
4
- export declare function getAccessTokenKey(): string;
5
- export declare function getRefreshTokenKey(): string;
@@ -1,20 +1,8 @@
1
1
  const DEFAULT_REALM_URL = 'https://idp.prod.apps.herodevs.io/realms/universe/protocol/openid-connect';
2
2
  const DEFAULT_CLIENT_ID = 'eol-ds';
3
- const DEFAULT_SERVICE_NAME = '@herodevs/cli';
4
- const DEFAULT_ACCESS_KEY = 'access-token';
5
- const DEFAULT_REFRESH_KEY = 'refresh-token';
6
3
  export function getRealmUrl() {
7
4
  return process.env.OAUTH_CONNECT_URL || DEFAULT_REALM_URL;
8
5
  }
9
6
  export function getClientId() {
10
7
  return process.env.OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
11
8
  }
12
- export function getTokenServiceName() {
13
- return process.env.HD_AUTH_SERVICE_NAME || DEFAULT_SERVICE_NAME;
14
- }
15
- export function getAccessTokenKey() {
16
- return process.env.HD_AUTH_ACCESS_KEY || DEFAULT_ACCESS_KEY;
17
- }
18
- export function getRefreshTokenKey() {
19
- return process.env.HD_AUTH_REFRESH_KEY || DEFAULT_REFRESH_KEY;
20
- }
@@ -5,7 +5,7 @@ export interface StoredTokens {
5
5
  export declare function saveTokens(tokens: {
6
6
  accessToken: string;
7
7
  refreshToken?: string;
8
- }): Promise<[void, unknown]>;
8
+ }): Promise<void>;
9
9
  export declare function getStoredTokens(): Promise<StoredTokens | undefined>;
10
- export declare function clearStoredTokens(): Promise<[unknown, unknown]>;
10
+ export declare function clearStoredTokens(): Promise<void>;
11
11
  export declare function isAccessTokenExpired(token: string | undefined): boolean;
@@ -1,42 +1,56 @@
1
- import { AsyncEntry } from '@napi-rs/keyring';
2
- import { getAccessTokenKey, getRefreshTokenKey, getTokenServiceName } from "./auth-config.svc.js";
1
+ import { createConfStore, decryptValue, encryptValue } from "./encrypted-store.svc.js";
3
2
  import { decodeJwtPayload } from "./jwt.svc.js";
3
+ import { debugLogger } from "./log.svc.js";
4
+ const AUTH_TOKEN_SALT = 'hdcli-auth-token-v1';
5
+ const ACCESS_TOKEN_KEY = 'accessToken';
6
+ const REFRESH_TOKEN_KEY = 'refreshToken';
4
7
  const TOKEN_SKEW_SECONDS = 30;
8
+ function getStore() {
9
+ return createConfStore('auth-token');
10
+ }
5
11
  export async function saveTokens(tokens) {
6
- const service = getTokenServiceName();
7
- const accessKey = getAccessTokenKey();
8
- const refreshKey = getRefreshTokenKey();
9
- const accessTokenSet = new AsyncEntry(service, accessKey).setPassword(tokens.accessToken);
10
- const refreshTokenSet = tokens.refreshToken
11
- ? new AsyncEntry(service, refreshKey).setPassword(tokens.refreshToken)
12
- : new AsyncEntry(service, refreshKey).deletePassword();
13
- return Promise.all([accessTokenSet, refreshTokenSet]);
12
+ const store = getStore();
13
+ store.set(ACCESS_TOKEN_KEY, encryptValue(tokens.accessToken, AUTH_TOKEN_SALT));
14
+ if (tokens.refreshToken) {
15
+ store.set(REFRESH_TOKEN_KEY, encryptValue(tokens.refreshToken, AUTH_TOKEN_SALT));
16
+ }
17
+ else {
18
+ store.delete(REFRESH_TOKEN_KEY);
19
+ }
14
20
  }
15
21
  export async function getStoredTokens() {
16
- const service = getTokenServiceName();
17
- const accessKey = getAccessTokenKey();
18
- const refreshKey = getRefreshTokenKey();
19
- return Promise.all([
20
- new AsyncEntry(service, accessKey).getPassword(),
21
- new AsyncEntry(service, refreshKey).getPassword(),
22
- ]).then(([accessToken, refreshToken]) => {
23
- if (!accessToken && !refreshToken) {
24
- return;
22
+ const store = getStore();
23
+ const encodedAccess = store.get(ACCESS_TOKEN_KEY);
24
+ const encodedRefresh = store.get(REFRESH_TOKEN_KEY);
25
+ let accessToken;
26
+ let refreshToken;
27
+ try {
28
+ if (encodedAccess && typeof encodedAccess === 'string') {
29
+ accessToken = decryptValue(encodedAccess, AUTH_TOKEN_SALT);
25
30
  }
26
- return {
27
- accessToken,
28
- refreshToken,
29
- };
30
- });
31
+ }
32
+ catch (error) {
33
+ debugLogger('Failed to decrypt access token: %O', error);
34
+ accessToken = undefined;
35
+ }
36
+ try {
37
+ if (encodedRefresh && typeof encodedRefresh === 'string') {
38
+ refreshToken = decryptValue(encodedRefresh, AUTH_TOKEN_SALT);
39
+ }
40
+ }
41
+ catch (error) {
42
+ debugLogger('Failed to decrypt refresh token: %O', error);
43
+ refreshToken = undefined;
44
+ }
45
+ if (!accessToken && !refreshToken) {
46
+ return;
47
+ }
48
+ return { accessToken, refreshToken };
31
49
  }
32
50
  export async function clearStoredTokens() {
33
- const service = getTokenServiceName();
34
- const accessKey = getAccessTokenKey();
35
- const refreshKey = getRefreshTokenKey();
36
- return Promise.all([
37
- new AsyncEntry(service, accessKey).deletePassword(),
38
- new AsyncEntry(service, refreshKey).deletePassword(),
39
- ]);
51
+ const store = getStore();
52
+ store.delete(ACCESS_TOKEN_KEY);
53
+ store.delete(REFRESH_TOKEN_KEY);
40
54
  }
41
55
  export function isAccessTokenExpired(token) {
42
56
  const payload = decodeJwtPayload(token);
@@ -1,48 +1,16 @@
1
- import crypto from 'node:crypto';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import Conf from 'conf';
5
1
  import { config } from "../config/constants.js";
2
+ import { createConfStore, decryptValue, encryptValue } from "./encrypted-store.svc.js";
3
+ import { debugLogger } from "./log.svc.js";
6
4
  const CI_TOKEN_STORAGE_KEY = 'ciRefreshToken';
7
- const ENCRYPTION_SALT = 'hdcli-ci-token-v1';
8
- const ALGORITHM = 'aes-256-gcm';
9
- const IV_LENGTH = 12;
10
- const AUTH_TAG_LENGTH = 16;
5
+ const CI_TOKEN_SALT = 'hdcli-ci-token-v1';
11
6
  function getConfStore() {
12
- const cwd = path.join(os.homedir(), '.hdcli');
13
- return new Conf({
14
- projectName: 'hdcli',
15
- cwd,
16
- configName: 'ci-token',
17
- });
18
- }
19
- function getMachineKey() {
20
- const hostname = os.hostname();
21
- const username = os.userInfo().username;
22
- const raw = `${hostname}:${username}:${ENCRYPTION_SALT}`;
23
- return crypto.createHash('sha256').update(raw, 'utf8').digest();
7
+ return createConfStore('ci-token');
24
8
  }
25
9
  export function encryptToken(plaintext) {
26
- const key = getMachineKey();
27
- const iv = crypto.randomBytes(IV_LENGTH);
28
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
29
- const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
30
- const authTag = cipher.getAuthTag();
31
- const combined = Buffer.concat([iv, authTag, encrypted]);
32
- return combined.toString('base64url');
10
+ return encryptValue(plaintext, CI_TOKEN_SALT);
33
11
  }
34
12
  export function decryptToken(encoded) {
35
- const key = getMachineKey();
36
- const combined = Buffer.from(encoded, 'base64url');
37
- if (combined.length < IV_LENGTH + AUTH_TAG_LENGTH) {
38
- throw new Error('Invalid encrypted token format');
39
- }
40
- const iv = combined.subarray(0, IV_LENGTH);
41
- const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
42
- const ciphertext = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
43
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
44
- decipher.setAuthTag(authTag);
45
- return decipher.update(ciphertext).toString('utf8') + decipher.final('utf8');
13
+ return decryptValue(encoded, CI_TOKEN_SALT);
46
14
  }
47
15
  export function getCITokenFromStorage() {
48
16
  const store = getConfStore();
@@ -53,7 +21,8 @@ export function getCITokenFromStorage() {
53
21
  try {
54
22
  return decryptToken(encoded);
55
23
  }
56
- catch {
24
+ catch (error) {
25
+ debugLogger('Failed to decrypt CI token: %O', error);
57
26
  return undefined;
58
27
  }
59
28
  }
@@ -0,0 +1,5 @@
1
+ import Conf from 'conf';
2
+ export declare function getMachineKey(salt: string): Buffer;
3
+ export declare function encryptValue(plaintext: string, salt: string): string;
4
+ export declare function decryptValue(encoded: string, salt: string): string;
5
+ export declare function createConfStore(configName: string): Conf<Record<string, unknown>>;
@@ -0,0 +1,43 @@
1
+ import crypto from 'node:crypto';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import Conf from 'conf';
5
+ const ALGORITHM = 'aes-256-gcm';
6
+ const IV_LENGTH = 12;
7
+ const AUTH_TAG_LENGTH = 16;
8
+ export function getMachineKey(salt) {
9
+ const hostname = os.hostname();
10
+ const username = os.userInfo().username;
11
+ const raw = `${hostname}:${username}:${salt}`;
12
+ return crypto.createHash('sha256').update(raw, 'utf8').digest();
13
+ }
14
+ export function encryptValue(plaintext, salt) {
15
+ const key = getMachineKey(salt);
16
+ const iv = crypto.randomBytes(IV_LENGTH);
17
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
18
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
19
+ const authTag = cipher.getAuthTag();
20
+ const combined = Buffer.concat([iv, authTag, encrypted]);
21
+ return combined.toString('base64url');
22
+ }
23
+ export function decryptValue(encoded, salt) {
24
+ const key = getMachineKey(salt);
25
+ const combined = Buffer.from(encoded, 'base64url');
26
+ if (combined.length < IV_LENGTH + AUTH_TAG_LENGTH) {
27
+ throw new Error('Invalid encrypted token format');
28
+ }
29
+ const iv = combined.subarray(0, IV_LENGTH);
30
+ const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
31
+ const ciphertext = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
32
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
33
+ decipher.setAuthTag(authTag);
34
+ return decipher.update(ciphertext).toString('utf8') + decipher.final('utf8');
35
+ }
36
+ export function createConfStore(configName) {
37
+ const cwd = path.join(os.homedir(), '.hdcli');
38
+ return new Conf({
39
+ projectName: 'hdcli',
40
+ cwd,
41
+ configName,
42
+ });
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herodevs/cli",
3
- "version": "2.0.0-beta.17",
3
+ "version": "2.0.0-beta.18",
4
4
  "author": "HeroDevs, Inc",
5
5
  "bin": {
6
6
  "hd": "./bin/run.js"
@@ -47,7 +47,6 @@
47
47
  "@cyclonedx/cdxgen": "^12.1.1",
48
48
  "@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.18",
49
49
  "@inquirer/prompts": "^8.0.2",
50
- "@napi-rs/keyring": "^1.2.0",
51
50
  "@oclif/core": "^4.8.0",
52
51
  "@oclif/plugin-help": "^6.2.32",
53
52
  "@oclif/plugin-update": "^4.7.16",