@aderis-erp/cli 1.0.14 → 1.0.17
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 +30 -8
- package/dist/commands/access-groups.d.ts +0 -1
- package/dist/commands/access-groups.js +22 -78
- package/dist/commands/account-transaction-allocations.d.ts +0 -1
- package/dist/commands/account-transaction-allocations.js +22 -78
- package/dist/commands/account-transactions.d.ts +0 -1
- package/dist/commands/account-transactions.js +28 -82
- package/dist/commands/accounts.d.ts +0 -1
- package/dist/commands/accounts.js +24 -80
- package/dist/commands/agent-platform.d.ts +0 -1
- package/dist/commands/agent-platform.js +24 -79
- package/dist/commands/analytics.d.ts +39 -0
- package/dist/commands/analytics.js +1113 -0
- package/dist/commands/{documents.d.ts → api-keys.d.ts} +1 -2
- package/dist/commands/api-keys.js +1117 -0
- package/dist/commands/audit-log.d.ts +0 -1
- package/dist/commands/audit-log.js +22 -78
- package/dist/commands/auth/login.d.ts +9 -0
- package/dist/commands/auth/login.js +83 -21
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +23 -6
- package/dist/commands/auth/status.js +46 -23
- package/dist/commands/auth/tenant.js +15 -9
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +37 -51
- package/dist/commands/bank-accounts.d.ts +0 -1
- package/dist/commands/bank-accounts.js +22 -78
- package/dist/commands/bank-integrations.d.ts +0 -1
- package/dist/commands/bank-integrations.js +22 -78
- package/dist/commands/business-partners.d.ts +1 -1
- package/dist/commands/business-partners.js +38 -84
- package/dist/commands/chat.d.ts +0 -1
- package/dist/commands/chat.js +22 -78
- package/dist/commands/config/add.d.ts +12 -0
- package/dist/commands/config/add.js +97 -17
- package/dist/commands/config/current.js +12 -4
- package/dist/commands/config/list.js +12 -4
- package/dist/commands/config/set-api-key.d.ts +10 -0
- package/dist/commands/config/set-api-key.js +49 -0
- package/dist/commands/config/use.js +3 -4
- package/dist/commands/credit-card-accounts.d.ts +0 -1
- package/dist/commands/credit-card-accounts.js +22 -78
- package/dist/commands/delivery-notes.d.ts +0 -1
- package/dist/commands/delivery-notes.js +29 -84
- package/dist/commands/dunning-configs.d.ts +0 -1
- package/dist/commands/dunning-configs.js +22 -78
- package/dist/commands/dunning-reminders.d.ts +0 -1
- package/dist/commands/dunning-reminders.js +22 -78
- package/dist/commands/dunning-runs.d.ts +0 -1
- package/dist/commands/dunning-runs.js +22 -78
- package/dist/commands/email-transactions.d.ts +0 -1
- package/dist/commands/email-transactions.js +22 -78
- package/dist/commands/employees.d.ts +0 -1
- package/dist/commands/employees.js +24 -80
- package/dist/commands/entity-configurations.d.ts +0 -1
- package/dist/commands/entity-configurations.js +22 -78
- package/dist/commands/event-processor-mappings.d.ts +0 -1
- package/dist/commands/event-processor-mappings.js +22 -78
- package/dist/commands/{files-internal.d.ts → files.d.ts} +1 -2
- package/dist/commands/{files-internal.js → files.js} +40 -90
- package/dist/commands/health.d.ts +0 -1
- package/dist/commands/health.js +22 -78
- package/dist/commands/inbox.d.ts +0 -1
- package/dist/commands/inbox.js +22 -78
- package/dist/commands/integrations.d.ts +0 -1
- package/dist/commands/integrations.js +22 -78
- package/dist/commands/item-categories.d.ts +0 -1
- package/dist/commands/item-categories.js +22 -78
- package/dist/commands/items.d.ts +0 -1
- package/dist/commands/items.js +24 -80
- package/dist/commands/journal-lines.d.ts +0 -1
- package/dist/commands/journal-lines.js +22 -78
- package/dist/commands/journals.d.ts +1 -1
- package/dist/commands/journals.js +29 -81
- package/dist/commands/ledgers.d.ts +0 -1
- package/dist/commands/ledgers.js +26 -79
- package/dist/commands/nango-integrations.d.ts +39 -0
- package/dist/commands/nango-integrations.js +1115 -0
- package/dist/commands/office-locations.d.ts +0 -1
- package/dist/commands/office-locations.js +22 -78
- package/dist/commands/payment-methods.d.ts +0 -1
- package/dist/commands/payment-methods.js +22 -78
- package/dist/commands/payment-runs.d.ts +0 -1
- package/dist/commands/payment-runs.js +24 -79
- package/dist/commands/payment-terms.d.ts +0 -1
- package/dist/commands/payment-terms.js +22 -78
- package/dist/commands/payments.d.ts +0 -1
- package/dist/commands/payments.js +22 -78
- package/dist/commands/price-lists.d.ts +0 -1
- package/dist/commands/price-lists.js +22 -78
- package/dist/commands/processor-mappings.d.ts +0 -1
- package/dist/commands/processor-mappings.js +22 -78
- package/dist/commands/processors.d.ts +0 -1
- package/dist/commands/processors.js +22 -78
- package/dist/commands/purchase-order-types.d.ts +0 -1
- package/dist/commands/purchase-order-types.js +22 -78
- package/dist/commands/purchase-orders.d.ts +1 -1
- package/dist/commands/purchase-orders.js +35 -84
- package/dist/commands/reports.d.ts +0 -1
- package/dist/commands/reports.js +23 -79
- package/dist/commands/salary-types.d.ts +0 -1
- package/dist/commands/salary-types.js +22 -78
- package/dist/commands/sales-invoices.d.ts +0 -1
- package/dist/commands/sales-invoices.js +38 -89
- package/dist/commands/sales-orders.d.ts +0 -1
- package/dist/commands/sales-orders.js +26 -82
- package/dist/commands/saved-views.d.ts +0 -1
- package/dist/commands/saved-views.js +22 -78
- package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
- package/dist/commands/scheduled-processor-mappings.js +22 -78
- package/dist/commands/schedules.d.ts +0 -1
- package/dist/commands/schedules.js +22 -78
- package/dist/commands/switch.js +14 -7
- package/dist/commands/tag-configurations.d.ts +39 -0
- package/dist/commands/{documents.js → tag-configurations.js} +40 -96
- package/dist/commands/tags.d.ts +0 -1
- package/dist/commands/tags.js +22 -78
- package/dist/commands/tax-codes.d.ts +0 -1
- package/dist/commands/tax-codes.js +22 -78
- package/dist/commands/tax-matrix.d.ts +0 -1
- package/dist/commands/tax-matrix.js +22 -78
- package/dist/commands/tenants.d.ts +0 -1
- package/dist/commands/tenants.js +24 -79
- package/dist/commands/text-templates.d.ts +0 -1
- package/dist/commands/text-templates.js +22 -78
- package/dist/commands/tools.d.ts +0 -1
- package/dist/commands/tools.js +22 -78
- package/dist/commands/vat-declarations.d.ts +1 -1
- package/dist/commands/vat-declarations.js +28 -79
- package/dist/commands/vendor-invoices.d.ts +0 -1
- package/dist/commands/vendor-invoices.js +31 -86
- package/dist/commands/webhooks.d.ts +0 -1
- package/dist/commands/webhooks.js +22 -78
- package/dist/commands/workflows.d.ts +0 -1
- package/dist/commands/workflows.js +22 -78
- package/dist/lib/api-client.js +8 -6
- package/dist/lib/auth/constants.d.ts +11 -0
- package/dist/lib/auth/constants.js +11 -0
- package/dist/lib/auth/errors.d.ts +17 -0
- package/dist/lib/auth/errors.js +22 -0
- package/dist/lib/auth/flow.d.ts +27 -0
- package/dist/lib/auth/flow.js +163 -0
- package/dist/lib/auth/guards.d.ts +5 -0
- package/dist/lib/auth/guards.js +14 -0
- package/dist/lib/auth/http.d.ts +16 -0
- package/dist/lib/auth/http.js +49 -0
- package/dist/lib/auth/index.d.ts +49 -0
- package/dist/lib/auth/index.js +217 -0
- package/dist/lib/auth/keycloak-provider.d.ts +75 -0
- package/dist/lib/auth/keycloak-provider.js +215 -0
- package/dist/lib/auth/pages.d.ts +25 -0
- package/dist/lib/auth/pages.js +123 -0
- package/dist/lib/auth/pkce.d.ts +11 -0
- package/dist/lib/auth/pkce.js +17 -0
- package/dist/lib/auth/tokens.d.ts +54 -0
- package/dist/lib/auth/tokens.js +104 -0
- package/dist/lib/auth/types.d.ts +57 -0
- package/dist/lib/auth/types.js +1 -0
- package/dist/lib/auth/validated-url.d.ts +6 -0
- package/dist/lib/auth/validated-url.js +53 -0
- package/dist/lib/base-api-command.js +6 -4
- package/dist/lib/config.d.ts +44 -4
- package/dist/lib/config.js +55 -19
- package/oclif.manifest.json +1181 -330
- package/package.json +3 -2
- package/dist/lib/auth.d.ts +0 -35
- package/dist/lib/auth.js +0 -566
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Context, type TokenData } from '../config.js';
|
|
2
|
+
export interface Tenant {
|
|
3
|
+
createdAt?: string;
|
|
4
|
+
isDefault?: boolean;
|
|
5
|
+
name: string;
|
|
6
|
+
tenantId: string;
|
|
7
|
+
}
|
|
8
|
+
/** Inputs to one authorization request. The flow generates them; the provider decides which to send. */
|
|
9
|
+
export interface AuthorizeParams {
|
|
10
|
+
/** PKCE S256 challenge derived from this round's verifier. */
|
|
11
|
+
codeChallenge: string;
|
|
12
|
+
/** OIDC nonce; providers that support it echo it in the id_token. */
|
|
13
|
+
nonce: string;
|
|
14
|
+
prompt?: 'login' | 'none';
|
|
15
|
+
state: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ExchangeParams {
|
|
18
|
+
code: string;
|
|
19
|
+
codeVerifier: string;
|
|
20
|
+
/** The nonce sent with the matching authorization request, for id_token verification. */
|
|
21
|
+
nonce: string;
|
|
22
|
+
}
|
|
23
|
+
/** One browser round of a login or tenant switch; the flow adds PKCE, state, nonce and the URL via the provider. */
|
|
24
|
+
export interface AuthRound {
|
|
25
|
+
/**
|
|
26
|
+
* Runs after this round's tokens are exchanged and stored. Tokens it returns
|
|
27
|
+
* replace them as the round's result and are stored too; a throw aborts the flow.
|
|
28
|
+
*/
|
|
29
|
+
afterExchange?: (tokens: TokenData) => Promise<TokenData | void>;
|
|
30
|
+
/** Forwarded to {@link AuthProvider.buildAuthorizeUrl} as `prompt`. */
|
|
31
|
+
prompt?: 'login' | 'none';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The seam between the login orchestration (`flow.ts`, `index.ts`) and Keycloak.
|
|
35
|
+
* The implementation owns endpoints, scopes and token semantics; it never starts
|
|
36
|
+
* servers or opens browsers.
|
|
37
|
+
*/
|
|
38
|
+
export interface AuthProvider {
|
|
39
|
+
/** The URL the browser is sent to. Async because Keycloak resolves endpoints via OIDC discovery. */
|
|
40
|
+
buildAuthorizeUrl(context: Context, params: AuthorizeParams): Promise<string>;
|
|
41
|
+
/** Redeem the authorization code. Must validate the id_token nonce when the provider issues one. */
|
|
42
|
+
exchangeCode(context: Context, params: ExchangeParams): Promise<TokenData>;
|
|
43
|
+
/** Tenants the user can switch to, given a valid access token. */
|
|
44
|
+
listTenants(context: Context, accessToken: string): Promise<Tenant[]>;
|
|
45
|
+
/** Refresh-token grant. A rejected refresh must surface as the "Session expired" error. */
|
|
46
|
+
refresh(context: Context, refreshToken: string): Promise<TokenData>;
|
|
47
|
+
/**
|
|
48
|
+
* Re-scopes a live session to `tenantId` with one token request and no
|
|
49
|
+
* browser. A rejected refresh must surface as the "Session expired" error.
|
|
50
|
+
*/
|
|
51
|
+
switchTenant(context: Context, refreshToken: string, tenantId: string): Promise<TokenData>;
|
|
52
|
+
/**
|
|
53
|
+
* The browser rounds that end with a token for `tenantId` when no live
|
|
54
|
+
* session exists; the flow appends the tenant assertion.
|
|
55
|
+
*/
|
|
56
|
+
tenantSwitchRounds(context: Context, tenantId: string): AuthRound[];
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates `baseUrl` against the host allowlist and optionally appends `path`.
|
|
3
|
+
* Throws `Error('Invalid URL')` for anything off-list, non-http(s), or
|
|
4
|
+
* containing path traversal.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildValidatedUrl(baseUrl: string, path?: string): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { URL } from 'node:url';
|
|
2
|
+
/**
|
|
3
|
+
* Hosts the CLI may talk to. Every outbound URL the auth layer builds — API,
|
|
4
|
+
* Keycloak discovery and the endpoints discovery returns — goes through
|
|
5
|
+
* {@link buildValidatedUrl}, so a tampered config file or discovery document
|
|
6
|
+
* cannot redirect tokens elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
const ALLOWED_HOSTS = new Set([
|
|
9
|
+
'aderis-erp-nonprod.cloud-iam.com',
|
|
10
|
+
'aderis-erp-prod.cloud-iam.com',
|
|
11
|
+
'api-dev.aderis.com',
|
|
12
|
+
'api-staging.aderis.com',
|
|
13
|
+
'api-test.aderis.com',
|
|
14
|
+
'api.aderis.com',
|
|
15
|
+
'iam-dev.aderis.com',
|
|
16
|
+
'iam-staging.aderis.com',
|
|
17
|
+
'iam-test.aderis.com',
|
|
18
|
+
'iam.aderis.com',
|
|
19
|
+
'localhost',
|
|
20
|
+
]);
|
|
21
|
+
function hasTraversal(segment) {
|
|
22
|
+
return segment.includes('/../') || /\/%2e%2e\//i.test(segment);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validates `baseUrl` against the host allowlist and optionally appends `path`.
|
|
26
|
+
* Throws `Error('Invalid URL')` for anything off-list, non-http(s), or
|
|
27
|
+
* containing path traversal.
|
|
28
|
+
*/
|
|
29
|
+
export function buildValidatedUrl(baseUrl, path) {
|
|
30
|
+
try {
|
|
31
|
+
if (hasTraversal(baseUrl)) {
|
|
32
|
+
throw new Error('Invalid path');
|
|
33
|
+
}
|
|
34
|
+
const url = new URL(baseUrl);
|
|
35
|
+
if (!ALLOWED_HOSTS.has(url.hostname)) {
|
|
36
|
+
throw new Error(`Invalid host: ${url.hostname}`);
|
|
37
|
+
}
|
|
38
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
39
|
+
throw new Error('Invalid protocol');
|
|
40
|
+
}
|
|
41
|
+
if (path) {
|
|
42
|
+
if (hasTraversal(path)) {
|
|
43
|
+
throw new Error('Invalid path');
|
|
44
|
+
}
|
|
45
|
+
const cleanPath = path.startsWith('/') ? path : '/' + path;
|
|
46
|
+
url.pathname = url.pathname.replace(/\/$/, '') + cleanPath;
|
|
47
|
+
}
|
|
48
|
+
return url.href;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new Error('Invalid URL');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { ApiError, apiRequest } from './api-client.js';
|
|
3
|
-
import { getCurrentContext } from './config.js';
|
|
3
|
+
import { getCurrentContext, hasCredentials, missingCredentialsMessage } from './config.js';
|
|
4
4
|
export class BaseApiCommand extends Command {
|
|
5
5
|
static globalFlags = {
|
|
6
6
|
context: Flags.string({
|
|
7
|
+
aliases: ['config'],
|
|
7
8
|
char: 'c',
|
|
8
|
-
description: 'Context to use
|
|
9
|
+
description: 'Context to use, overriding the current context (alias: --config)',
|
|
10
|
+
env: 'ADERIS_CONTEXT',
|
|
9
11
|
}),
|
|
10
12
|
output: Flags.string({
|
|
11
13
|
char: 'o',
|
|
@@ -66,8 +68,8 @@ export class BaseApiCommand extends Command {
|
|
|
66
68
|
if (!context) {
|
|
67
69
|
this.error('No context configured. Run: aderis auth login --new');
|
|
68
70
|
}
|
|
69
|
-
if (!context
|
|
70
|
-
this.error(
|
|
71
|
+
if (!hasCredentials(context)) {
|
|
72
|
+
this.error(missingCredentialsMessage(context));
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
printTable(data) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
|
-
export type Environment = 'dev' | 'live' | 'staging';
|
|
2
|
+
export type Environment = 'dev' | 'live' | 'staging' | 'test';
|
|
3
|
+
/**
|
|
4
|
+
* How a context authenticates against the API.
|
|
5
|
+
*
|
|
6
|
+
* - `oauth2`: browser PKCE login, sends `Authorization: Bearer <access_token>`
|
|
7
|
+
* - `api_key`: a long-lived key, sends `x-api-key: <key>`
|
|
8
|
+
*
|
|
9
|
+
* Contexts created before API-key support have no `auth_type` field; they are
|
|
10
|
+
* treated as `oauth2`, so always read it through {@link getAuthType}.
|
|
11
|
+
*/
|
|
12
|
+
export type AuthType = 'api_key' | 'oauth2';
|
|
3
13
|
export interface BaseUrls {
|
|
4
14
|
api: string;
|
|
5
|
-
auth: string;
|
|
6
15
|
}
|
|
7
16
|
export declare const BASE_URLS: Record<Environment, BaseUrls>;
|
|
8
|
-
|
|
17
|
+
/** The Keycloak CLI client: one public client (PKCE S256, loopback redirect) in every realm. */
|
|
18
|
+
export declare const DEFAULT_KEYCLOAK_CLIENT_ID = "aderis-cli-login";
|
|
9
19
|
export interface TokenData {
|
|
10
20
|
access_token: string;
|
|
11
21
|
expires_at?: number;
|
|
@@ -17,10 +27,19 @@ export interface CachedTenant {
|
|
|
17
27
|
tenantId: string;
|
|
18
28
|
}
|
|
19
29
|
export interface Context {
|
|
30
|
+
api_key?: string;
|
|
31
|
+
auth_type?: AuthType;
|
|
20
32
|
cachedTenants?: CachedTenant[];
|
|
21
|
-
|
|
33
|
+
/** Only required for `oauth2` contexts; API-key contexts have no OAuth client. */
|
|
34
|
+
client_id?: string;
|
|
22
35
|
client_secret?: string;
|
|
23
36
|
environment: Environment;
|
|
37
|
+
/**
|
|
38
|
+
* Keycloak origin to use instead of the environment's default host
|
|
39
|
+
* (`keycloak` contexts only; https, allowlisted, origin without a path). The
|
|
40
|
+
* realm is still the environment's.
|
|
41
|
+
*/
|
|
42
|
+
keycloak_base_url?: string;
|
|
24
43
|
name: string;
|
|
25
44
|
tenant_id?: string;
|
|
26
45
|
tokens?: TokenData;
|
|
@@ -44,5 +63,26 @@ export declare function saveCachedTenants(contextName: string, tenants: CachedTe
|
|
|
44
63
|
export declare function getCachedTenants(contextName: string): CachedTenant[] | undefined;
|
|
45
64
|
export declare function getTokens(contextName: string): null | TokenData;
|
|
46
65
|
export declare function clearTokens(contextName: string): void;
|
|
66
|
+
export declare function saveApiKey(contextName: string, apiKey: string): void;
|
|
67
|
+
export declare function clearApiKey(contextName: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Auth type of a context, defaulting to `oauth2` for contexts stored before
|
|
70
|
+
* API-key support existed.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getAuthType(context: Context): AuthType;
|
|
73
|
+
/** Whether a context holds the credential its auth type needs to call the API. */
|
|
74
|
+
export declare function hasCredentials(context: Context): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The error to show when a context can't authenticate. Kept here so every
|
|
77
|
+
* command — including the ~60 generated ones — points at the right remedy for
|
|
78
|
+
* the context's auth type instead of always suggesting `auth login`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function missingCredentialsMessage(context: Context): string;
|
|
81
|
+
/** Error message for an expired OAuth session; `aderis switch` matches on it to offer a re-login. */
|
|
82
|
+
export declare const SESSION_EXPIRED_MESSAGE = "Session expired. Please run: aderis auth login";
|
|
83
|
+
/** The error for a 401 on a context that holds a credential. */
|
|
84
|
+
export declare function unauthorizedMessage(context: Context): string;
|
|
85
|
+
/** Redact a key for display: first and last 4 characters only. */
|
|
86
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
47
87
|
export declare function getBaseUrls(environment: Environment): BaseUrls;
|
|
48
88
|
export { config };
|
package/dist/lib/config.js
CHANGED
|
@@ -3,26 +3,21 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
export const BASE_URLS = {
|
|
6
|
-
dev: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
live: {
|
|
11
|
-
api: 'https://api.aderis.com',
|
|
12
|
-
auth: 'https://auth.aderis.com',
|
|
13
|
-
},
|
|
14
|
-
staging: {
|
|
15
|
-
api: 'https://api-staging.aderis.com',
|
|
16
|
-
auth: 'https://auth-staging.aderis.com',
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
// Default OAuth client IDs per environment (public clients, no secret required)
|
|
20
|
-
export const DEFAULT_CLIENT_IDS = {
|
|
21
|
-
dev: '74fd96f8-cf4b-49d8-b713-2a121ea22910',
|
|
22
|
-
live: '970d9568-84c9-4613-b966-30d7e1d93828',
|
|
23
|
-
staging: 'bf0e5dd6-419f-4d64-8951-2c385533651f',
|
|
6
|
+
dev: { api: 'https://api-dev.aderis.com' },
|
|
7
|
+
live: { api: 'https://api.aderis.com' },
|
|
8
|
+
staging: { api: 'https://api-staging.aderis.com' },
|
|
9
|
+
test: { api: 'https://api-test.aderis.com' },
|
|
24
10
|
};
|
|
25
|
-
|
|
11
|
+
/** The Keycloak CLI client: one public client (PKCE S256, loopback redirect) in every realm. */
|
|
12
|
+
export const DEFAULT_KEYCLOAK_CLIENT_ID = 'aderis-cli-login';
|
|
13
|
+
/**
|
|
14
|
+
* Directory of `config.json`: `ADERIS_CONFIG_DIR`, resolved to an absolute path
|
|
15
|
+
* so a relative value does not depend on each command's working directory, or
|
|
16
|
+
* `~/.aderis`.
|
|
17
|
+
*/
|
|
18
|
+
const CONFIG_DIR = process.env.ADERIS_CONFIG_DIR
|
|
19
|
+
? path.resolve(process.env.ADERIS_CONFIG_DIR)
|
|
20
|
+
: path.join(os.homedir(), '.aderis');
|
|
26
21
|
// Ensure config directory exists
|
|
27
22
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
23
|
fs.mkdirSync(CONFIG_DIR, { mode: 0o700, recursive: true });
|
|
@@ -133,6 +128,47 @@ export function getTokens(contextName) {
|
|
|
133
128
|
export function clearTokens(contextName) {
|
|
134
129
|
updateContext(contextName, { tokens: undefined });
|
|
135
130
|
}
|
|
131
|
+
export function saveApiKey(contextName, apiKey) {
|
|
132
|
+
updateContext(contextName, { api_key: apiKey, auth_type: 'api_key' });
|
|
133
|
+
}
|
|
134
|
+
export function clearApiKey(contextName) {
|
|
135
|
+
updateContext(contextName, { api_key: undefined });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Auth type of a context, defaulting to `oauth2` for contexts stored before
|
|
139
|
+
* API-key support existed.
|
|
140
|
+
*/
|
|
141
|
+
export function getAuthType(context) {
|
|
142
|
+
return context.auth_type ?? 'oauth2';
|
|
143
|
+
}
|
|
144
|
+
/** Whether a context holds the credential its auth type needs to call the API. */
|
|
145
|
+
export function hasCredentials(context) {
|
|
146
|
+
return getAuthType(context) === 'api_key' ? Boolean(context.api_key) : Boolean(context.tokens);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The error to show when a context can't authenticate. Kept here so every
|
|
150
|
+
* command — including the ~60 generated ones — points at the right remedy for
|
|
151
|
+
* the context's auth type instead of always suggesting `auth login`.
|
|
152
|
+
*/
|
|
153
|
+
export function missingCredentialsMessage(context) {
|
|
154
|
+
return getAuthType(context) === 'api_key'
|
|
155
|
+
? `No API key set for context "${context.name}". Run: aderis config set-api-key --context ${context.name}`
|
|
156
|
+
: `Not logged in to context "${context.name}". Run: aderis auth login --context ${context.name}`;
|
|
157
|
+
}
|
|
158
|
+
/** Error message for an expired OAuth session; `aderis switch` matches on it to offer a re-login. */
|
|
159
|
+
export const SESSION_EXPIRED_MESSAGE = 'Session expired. Please run: aderis auth login';
|
|
160
|
+
/** The error for a 401 on a context that holds a credential. */
|
|
161
|
+
export function unauthorizedMessage(context) {
|
|
162
|
+
if (getAuthType(context) === 'api_key') {
|
|
163
|
+
return `API key for context "${context.name}" was rejected. Check the key with: aderis config set-api-key --context ${context.name}`;
|
|
164
|
+
}
|
|
165
|
+
// TODO(infrastructure-stacks#152): return SESSION_EXPIRED_MESSAGE once the API accepts iam-* issuers.
|
|
166
|
+
return 'Session rejected by the API (issuer or audience mismatch) — see https://github.com/AderisERP/infrastructure-stacks/issues/152, or run: aderis auth login';
|
|
167
|
+
}
|
|
168
|
+
/** Redact a key for display: first and last 4 characters only. */
|
|
169
|
+
export function maskApiKey(apiKey) {
|
|
170
|
+
return apiKey.length <= 8 ? '*'.repeat(apiKey.length) : `${apiKey.slice(0, 4)}${'*'.repeat(8)}${apiKey.slice(-4)}`;
|
|
171
|
+
}
|
|
136
172
|
export function getBaseUrls(environment) {
|
|
137
173
|
return BASE_URLS[environment];
|
|
138
174
|
}
|