@abgov/nx-oc 13.1.0 → 13.2.0-beta.2
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 +2 -2
- package/package.json +2 -4
- package/src/adsp/adsp-utils.d.ts +18 -5
- package/src/adsp/adsp-utils.js +116 -108
- package/src/adsp/adsp-utils.js.map +1 -1
- package/src/adsp/token-cache.d.ts +0 -9
- package/src/adsp/token-cache.js +0 -49
- package/src/adsp/token-cache.js.map +0 -1
- package/src/adsp/token-cache.spec.ts +0 -117
package/README.md
CHANGED
|
@@ -143,8 +143,8 @@ npx nx run my-app:deploy
|
|
|
143
143
|
|
|
144
144
|
For rapid iteration, the `sandbox` generator wires a per-app deploy that builds
|
|
145
145
|
the image **locally with podman**, pushes it to a container registry (GHCR), and
|
|
146
|
-
imports it into your OpenShift namespace — no git push or CI wait.
|
|
147
|
-
|
|
146
|
+
imports it into your OpenShift namespace — no git push or CI wait. (Requires the
|
|
147
|
+
Nx 23 line, `@abgov/nx-oc` 13.x.)
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
150
|
# Add the sandbox targets to a project (run once per app)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abgov/nx-oc",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.2.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"description": "Government of Alberta - Nx plugin for OpenShift.",
|
|
@@ -14,11 +14,9 @@
|
|
|
14
14
|
"tslib": "^2.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@abgov/adsp-cli": "^1.1.1",
|
|
17
18
|
"axios": "^1.16.0",
|
|
18
19
|
"enquirer": "^2.3.6",
|
|
19
|
-
"express": "^4.18.2",
|
|
20
|
-
"open": "^8.4.0",
|
|
21
|
-
"simple-oauth2": "^5.0.0",
|
|
22
20
|
"yaml": "~2.9.0"
|
|
23
21
|
},
|
|
24
22
|
"generators": "./generators.json",
|
package/src/adsp/adsp-utils.d.ts
CHANGED
|
@@ -2,12 +2,25 @@ import { Tree } from '@nx/devkit';
|
|
|
2
2
|
import { AdspConfiguration, AdspOptions } from './adsp';
|
|
3
3
|
import { EnvironmentName } from './environments';
|
|
4
4
|
export declare function hasDependency(host: Tree, dependency: string): boolean;
|
|
5
|
-
export declare
|
|
5
|
+
export declare const ADSP_ADMIN_SCOPE = "adsp-cli-admin";
|
|
6
|
+
/**
|
|
7
|
+
* Obtain an ADSP access token via @abgov/adsp-cli. Fast path: a cached/refreshed
|
|
8
|
+
* token from a prior `adsp login` (no browser). If none — and the run is
|
|
9
|
+
* interactive — drive `adsp login` (browser) once, then read the fresh token.
|
|
10
|
+
* In a non-interactive run it never opens a browser; it throws with the exact
|
|
11
|
+
* `adsp login` command to run instead.
|
|
12
|
+
*
|
|
13
|
+
* When `scopes` (e.g. the admin scope) can't be satisfied even after a login —
|
|
14
|
+
* a user who isn't a realm admin — it falls back to a base-scope token so
|
|
15
|
+
* non-provisioning steps still proceed (provisioning then degrades via 401/403).
|
|
16
|
+
*/
|
|
17
|
+
export declare function ensureAdspToken(options: {
|
|
18
|
+
env: EnvironmentName;
|
|
19
|
+
realm?: string;
|
|
20
|
+
tenant?: string;
|
|
21
|
+
scopes?: string[];
|
|
22
|
+
}): Promise<string | undefined>;
|
|
6
23
|
export declare function getServiceUrls(directoryUrl: string): Promise<Record<string, string>>;
|
|
7
|
-
export declare function selectTenant(tenantServiceUrl: string, token: string): Promise<{
|
|
8
|
-
name: string;
|
|
9
|
-
realm: string;
|
|
10
|
-
}>;
|
|
11
24
|
export declare function getAdspConfiguration(_host: Tree, options: {
|
|
12
25
|
env: EnvironmentName;
|
|
13
26
|
accessToken?: string;
|
package/src/adsp/adsp-utils.js
CHANGED
|
@@ -1,94 +1,114 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ADSP_ADMIN_SCOPE = void 0;
|
|
3
4
|
exports.hasDependency = hasDependency;
|
|
4
|
-
exports.
|
|
5
|
+
exports.ensureAdspToken = ensureAdspToken;
|
|
5
6
|
exports.getServiceUrls = getServiceUrls;
|
|
6
|
-
exports.selectTenant = selectTenant;
|
|
7
7
|
exports.getAdspConfiguration = getAdspConfiguration;
|
|
8
8
|
exports.isAdspOptions = isAdspOptions;
|
|
9
9
|
exports.addClientRedirectUris = addClientRedirectUris;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
11
|
const devkit_1 = require("@nx/devkit");
|
|
12
12
|
const axios_1 = require("axios");
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const module_1 = require("module");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const adsp_cli_1 = require("@abgov/adsp-cli");
|
|
17
18
|
const environments_1 = require("./environments");
|
|
18
|
-
const token_cache_1 = require("./token-cache");
|
|
19
19
|
function hasDependency(host, dependency) {
|
|
20
20
|
const { dependencies, devDependencies } = (0, devkit_1.readJson)(host, 'package.json');
|
|
21
21
|
return !!(dependencies === null || dependencies === void 0 ? void 0 : dependencies[dependency]) || !!(devDependencies === null || devDependencies === void 0 ? void 0 : devDependencies[dependency]);
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
// Scope requested for generator work that provisions Keycloak (creating a
|
|
24
|
+
// service's OAuth client/roles, registering redirect URIs). It's an optional
|
|
25
|
+
// client scope on the `adsp-cli` client — only present in a token when a login
|
|
26
|
+
// explicitly requests it, and only effective for users who already have the
|
|
27
|
+
// underlying realm-admin grant. keycloak-admin.ts tolerates the 401/403 for
|
|
28
|
+
// everyone else, so requesting it is always safe.
|
|
29
|
+
exports.ADSP_ADMIN_SCOPE = 'adsp-cli-admin';
|
|
30
|
+
// Mirrors nx-adsp's isNonInteractive (utils/agent.ts) — duplicated because nx-oc
|
|
31
|
+
// is the lower-level package and can't import from nx-adsp. Keep them in sync.
|
|
32
|
+
function isNonInteractive() {
|
|
33
|
+
const argv = process.argv;
|
|
34
|
+
const interactiveIdx = argv.indexOf('--interactive');
|
|
35
|
+
const flagged = argv.includes('--no-interactive') ||
|
|
36
|
+
argv.includes('--interactive=false') ||
|
|
37
|
+
(interactiveIdx !== -1 && argv[interactiveIdx + 1] === 'false');
|
|
38
|
+
return flagged || !process.stdout.isTTY || process.env.CI === 'true';
|
|
39
|
+
}
|
|
40
|
+
/** Resolve the `adsp` binary shipped by the @abgov/adsp-cli dependency. */
|
|
41
|
+
function adspCliBinPath() {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
const require = (0, module_1.createRequire)(__filename);
|
|
44
|
+
const pkgJsonPath = require.resolve('@abgov/adsp-cli/package.json');
|
|
45
|
+
const binRel = (_b = (_a = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).bin) === null || _a === void 0 ? void 0 : _a.adsp) !== null && _b !== void 0 ? _b : 'src/main.js';
|
|
46
|
+
return path.join(path.dirname(pkgJsonPath), binRel);
|
|
47
|
+
}
|
|
48
|
+
/** Drive the CLI's interactive browser login as a subprocess (the sanctioned way
|
|
49
|
+
* to trigger it — the interactive flow is intentionally not a library export).
|
|
50
|
+
* stdio is inherited so the browser-open message and the no-args tenant picker
|
|
51
|
+
* are visible to the user, exactly like the previous inline flow. */
|
|
52
|
+
function runAdspLogin(options) {
|
|
53
|
+
const args = ['login', '--env', options.env];
|
|
54
|
+
if (options.realm)
|
|
55
|
+
args.push('--realm', options.realm);
|
|
56
|
+
else if (options.tenant)
|
|
57
|
+
args.push('--tenant', options.tenant);
|
|
58
|
+
for (const scope of options.scopes)
|
|
59
|
+
args.push('--scope', scope);
|
|
60
|
+
(0, child_process_1.spawnSync)(process.execPath, [adspCliBinPath(), ...args], { stdio: 'inherit' });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Obtain an ADSP access token via @abgov/adsp-cli. Fast path: a cached/refreshed
|
|
64
|
+
* token from a prior `adsp login` (no browser). If none — and the run is
|
|
65
|
+
* interactive — drive `adsp login` (browser) once, then read the fresh token.
|
|
66
|
+
* In a non-interactive run it never opens a browser; it throws with the exact
|
|
67
|
+
* `adsp login` command to run instead.
|
|
68
|
+
*
|
|
69
|
+
* When `scopes` (e.g. the admin scope) can't be satisfied even after a login —
|
|
70
|
+
* a user who isn't a realm admin — it falls back to a base-scope token so
|
|
71
|
+
* non-provisioning steps still proceed (provisioning then degrades via 401/403).
|
|
72
|
+
*/
|
|
73
|
+
function ensureAdspToken(options) {
|
|
24
74
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
75
|
+
const { env, realm, tenant, scopes = [] } = options;
|
|
76
|
+
// Force the CLI to talk to the generator's target environment. Note: we set
|
|
77
|
+
// ADSP_ENV, never ADSP_TENANT_REALM — the latter would make getStatus() drop
|
|
78
|
+
// the persisted tenantName (it can't trust a name against an overridden realm).
|
|
79
|
+
// Default to 'test' (UAT), never 'prod' — matches every generator's schema
|
|
80
|
+
// default and avoids an accidental production hit if env is ever unset.
|
|
81
|
+
process.env.ADSP_ENV = env || process.env.ADSP_ENV || 'test';
|
|
82
|
+
const fetchToken = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
const result = yield (0, adsp_cli_1.getAccessToken)(scopes.length ? { scopes } : undefined);
|
|
84
|
+
return result.status === 'ok' ? result.token : undefined;
|
|
36
85
|
});
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
(0, token_cache_1.setCachedToken)(accessServiceUrl, realm, token['access_token'], token['refresh_token'], expiresIn);
|
|
51
|
-
return token['access_token'];
|
|
52
|
-
}
|
|
53
|
-
catch (_c) {
|
|
54
|
-
// Refresh failed — fall through to browser login.
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
86
|
+
let token = yield fetchToken();
|
|
87
|
+
if (token)
|
|
88
|
+
return token;
|
|
89
|
+
if (isNonInteractive()) {
|
|
90
|
+
const loginCmd = [
|
|
91
|
+
'npx @abgov/adsp-cli login',
|
|
92
|
+
`--env ${env}`,
|
|
93
|
+
tenant ? `--tenant "${tenant}"` : realm ? `--realm ${realm}` : '',
|
|
94
|
+
...scopes.map((s) => `--scope ${s}`),
|
|
95
|
+
]
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.join(' ');
|
|
98
|
+
throw new Error(`Not signed in to ADSP (non-interactive run). Sign in first with:\n ${loginCmd}`);
|
|
58
99
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
res.send('Login failed.');
|
|
70
|
-
reject(new Error(`Error encountered during login. ${error}`));
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
res.send('Successfully signed in. You can close this browser tab or window.');
|
|
74
|
-
resolve(client.getToken({
|
|
75
|
-
code: code,
|
|
76
|
-
redirect_uri,
|
|
77
|
-
}));
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
const server = app.listen(3000);
|
|
82
|
-
open(authorizationUri);
|
|
83
|
-
const { token } = yield Promise.race([
|
|
84
|
-
tokenPromise,
|
|
85
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out waiting for login.')), 120000)),
|
|
86
|
-
]).finally(() => server.close());
|
|
87
|
-
if (realm !== 'core') {
|
|
88
|
-
const expiresIn = (_b = token['expires_in']) !== null && _b !== void 0 ? _b : 300;
|
|
89
|
-
(0, token_cache_1.setCachedToken)(accessServiceUrl, realm, token['access_token'], token['refresh_token'], expiresIn);
|
|
100
|
+
runAdspLogin({ env, realm, tenant, scopes });
|
|
101
|
+
token = yield fetchToken();
|
|
102
|
+
if (token)
|
|
103
|
+
return token;
|
|
104
|
+
// The requested scope wasn't granted (e.g. non-admin user). Fall back to a
|
|
105
|
+
// base-scope token so the rest of generation still works.
|
|
106
|
+
if (scopes.length) {
|
|
107
|
+
const base = yield (0, adsp_cli_1.getAccessToken)();
|
|
108
|
+
if (base.status === 'ok')
|
|
109
|
+
return base.token;
|
|
90
110
|
}
|
|
91
|
-
return
|
|
111
|
+
return undefined;
|
|
92
112
|
});
|
|
93
113
|
}
|
|
94
114
|
function getServiceUrls(directoryUrl) {
|
|
@@ -98,46 +118,29 @@ function getServiceUrls(directoryUrl) {
|
|
|
98
118
|
return urls;
|
|
99
119
|
});
|
|
100
120
|
}
|
|
101
|
-
function selectTenant(tenantServiceUrl, token) {
|
|
102
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
103
|
-
const { data: tenants } = yield axios_1.default.get(new URL('v2/tenants', tenantServiceUrl).href, {
|
|
104
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
105
|
-
});
|
|
106
|
-
const choices = tenants.results
|
|
107
|
-
.map((r) => r.name)
|
|
108
|
-
.sort((a, b) => a.localeCompare(b));
|
|
109
|
-
const result = yield (0, enquirer_1.prompt)({
|
|
110
|
-
type: 'autocomplete',
|
|
111
|
-
name: 'tenant',
|
|
112
|
-
message: 'Which tenant is the application or service for?',
|
|
113
|
-
choices,
|
|
114
|
-
});
|
|
115
|
-
const tenant = tenants.results.find((r) => r.name === result.tenant);
|
|
116
|
-
return tenant;
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
121
|
function getAdspConfiguration(_host, options) {
|
|
120
122
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
121
|
-
var _a;
|
|
123
|
+
var _a, _b;
|
|
122
124
|
const { env, accessToken } = options;
|
|
123
|
-
const environment = environments_1.environments[env || '
|
|
125
|
+
const environment = environments_1.environments[env || 'test'];
|
|
124
126
|
if (isAdspOptions(options)) {
|
|
125
127
|
// Adsp configuration already resolved — return it directly.
|
|
126
128
|
return options.adsp;
|
|
127
129
|
}
|
|
128
|
-
const urls = yield getServiceUrls(environment.directoryServiceUrl);
|
|
129
|
-
const tenantServiceUrl = urls['urn:ads:platform:tenant-service:v2'];
|
|
130
130
|
if (options.tenant) {
|
|
131
|
-
// --tenant <name>:
|
|
132
|
-
//
|
|
133
|
-
// access
|
|
131
|
+
// --tenant <name>: resolve the realm anonymously (no token needed), then get
|
|
132
|
+
// a token for that realm via the CLI. The token works for ADSP configuration
|
|
133
|
+
// access, the agent-service (agent-user role), and — with the admin scope —
|
|
134
|
+
// Keycloak client provisioning.
|
|
135
|
+
const urls = yield getServiceUrls(environment.directoryServiceUrl);
|
|
136
|
+
const tenantServiceUrl = urls['urn:ads:platform:tenant-service:v2'];
|
|
134
137
|
const { data } = yield axios_1.default.get(new URL('v2/tenants', tenantServiceUrl).href, { params: { name: options.tenant } });
|
|
135
138
|
const found = data.results[0];
|
|
136
139
|
if (!found) {
|
|
137
140
|
throw new Error(`Tenant '${options.tenant}' not found.`);
|
|
138
141
|
}
|
|
139
142
|
const realm = (_a = options.tenantRealm) !== null && _a !== void 0 ? _a : found.realm;
|
|
140
|
-
const token = accessToken
|
|
143
|
+
const token = accessToken !== null && accessToken !== void 0 ? accessToken : (yield ensureAdspToken({ env, realm, tenant: options.tenant, scopes: [exports.ADSP_ADMIN_SCOPE] }));
|
|
141
144
|
return {
|
|
142
145
|
tenant: (0, devkit_1.names)(found.name).fileName,
|
|
143
146
|
tenantRealm: realm,
|
|
@@ -146,17 +149,22 @@ function getAdspConfiguration(_host, options) {
|
|
|
146
149
|
accessToken: token,
|
|
147
150
|
};
|
|
148
151
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
directoryServiceUrl: environment.directoryServiceUrl,
|
|
158
|
-
};
|
|
152
|
+
// No tenant given: let the CLI resolve the tenant (its no-args login lists
|
|
153
|
+
// tenants and prompts), then read the resolved realm + tenant name back from
|
|
154
|
+
// its persisted context via getStatus() — no token-passing, no config-file
|
|
155
|
+
// spelunking.
|
|
156
|
+
const token = accessToken !== null && accessToken !== void 0 ? accessToken : (yield ensureAdspToken({ env, scopes: [exports.ADSP_ADMIN_SCOPE] }));
|
|
157
|
+
const status = (0, adsp_cli_1.getStatus)();
|
|
158
|
+
if (!status.realm) {
|
|
159
|
+
throw new Error('Could not determine the ADSP tenant. Run `npx @abgov/adsp-cli login` and retry.');
|
|
159
160
|
}
|
|
161
|
+
return {
|
|
162
|
+
tenant: (0, devkit_1.names)((_b = status.tenantName) !== null && _b !== void 0 ? _b : status.realm).fileName,
|
|
163
|
+
tenantRealm: status.realm,
|
|
164
|
+
accessServiceUrl: environment.accessServiceUrl,
|
|
165
|
+
directoryServiceUrl: environment.directoryServiceUrl,
|
|
166
|
+
accessToken: token,
|
|
167
|
+
};
|
|
160
168
|
});
|
|
161
169
|
}
|
|
162
170
|
function isAdspOptions(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adsp-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/adsp-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adsp-utils.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/adsp-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface CacheEntry {
|
|
2
|
-
accessToken: string;
|
|
3
|
-
refreshToken?: string;
|
|
4
|
-
expiresAt: string;
|
|
5
|
-
}
|
|
6
|
-
export declare function getCacheFilePath(): string;
|
|
7
|
-
export declare function isExpired(entry: CacheEntry): boolean;
|
|
8
|
-
export declare function getCachedToken(accessServiceUrl: string, realm: string): CacheEntry | null;
|
|
9
|
-
export declare function setCachedToken(accessServiceUrl: string, realm: string, accessToken: string, refreshToken: string | undefined, expiresIn: number): void;
|
package/src/adsp/token-cache.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getCacheFilePath = getCacheFilePath;
|
|
4
|
-
exports.isExpired = isExpired;
|
|
5
|
-
exports.getCachedToken = getCachedToken;
|
|
6
|
-
exports.setCachedToken = setCachedToken;
|
|
7
|
-
const fs = require("fs");
|
|
8
|
-
const os = require("os");
|
|
9
|
-
const path = require("path");
|
|
10
|
-
const EXPIRY_BUFFER_MS = 60000;
|
|
11
|
-
function getCacheFilePath() {
|
|
12
|
-
return path.join(os.homedir(), '.nx-adsp', 'token-cache.json');
|
|
13
|
-
}
|
|
14
|
-
function readCache() {
|
|
15
|
-
try {
|
|
16
|
-
return JSON.parse(fs.readFileSync(getCacheFilePath(), 'utf-8'));
|
|
17
|
-
}
|
|
18
|
-
catch (_a) {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function writeCache(cache) {
|
|
23
|
-
const filePath = getCacheFilePath();
|
|
24
|
-
const dir = path.dirname(filePath);
|
|
25
|
-
if (!fs.existsSync(dir)) {
|
|
26
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
27
|
-
}
|
|
28
|
-
fs.writeFileSync(filePath, JSON.stringify(cache, null, 2), { mode: 0o600, encoding: 'utf-8' });
|
|
29
|
-
}
|
|
30
|
-
function cacheKey(accessServiceUrl, realm) {
|
|
31
|
-
return `${accessServiceUrl}::${realm}`;
|
|
32
|
-
}
|
|
33
|
-
function isExpired(entry) {
|
|
34
|
-
return new Date(entry.expiresAt).getTime() - EXPIRY_BUFFER_MS < Date.now();
|
|
35
|
-
}
|
|
36
|
-
function getCachedToken(accessServiceUrl, realm) {
|
|
37
|
-
var _a;
|
|
38
|
-
return (_a = readCache()[cacheKey(accessServiceUrl, realm)]) !== null && _a !== void 0 ? _a : null;
|
|
39
|
-
}
|
|
40
|
-
function setCachedToken(accessServiceUrl, realm, accessToken, refreshToken, expiresIn) {
|
|
41
|
-
const cache = readCache();
|
|
42
|
-
cache[cacheKey(accessServiceUrl, realm)] = {
|
|
43
|
-
accessToken,
|
|
44
|
-
refreshToken,
|
|
45
|
-
expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),
|
|
46
|
-
};
|
|
47
|
-
writeCache(cache);
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=token-cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../../../../../packages/nx-oc/src/adsp/token-cache.ts"],"names":[],"mappings":";;AAcA,4CAEC;AAuBD,8BAEC;AAED,wCAEC;AAED,wCAcC;AA7DD,yBAAyB;AACzB,yBAAyB;AACzB,6BAA6B;AAU7B,MAAM,gBAAgB,GAAG,KAAM,CAAC;AAEhC,SAAgB,gBAAgB;IAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,OAAO,CAAC,CAAe,CAAC;IAChF,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB;IACnC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,QAAQ,CAAC,gBAAwB,EAAE,KAAa;IACvD,OAAO,GAAG,gBAAgB,KAAK,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,SAAgB,SAAS,CAAC,KAAiB;IACzC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAC7E,CAAC;AAED,SAAgB,cAAc,CAAC,gBAAwB,EAAE,KAAa;;IACpE,OAAO,MAAA,SAAS,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,mCAAI,IAAI,CAAC;AAChE,CAAC;AAED,SAAgB,cAAc,CAC5B,gBAAwB,EACxB,KAAa,EACb,WAAmB,EACnB,YAAgC,EAChC,SAAiB;IAEjB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,KAAK,CAAC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,GAAG;QACzC,WAAW;QACX,YAAY;QACZ,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;KACjE,CAAC;IACF,UAAU,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { getCacheFilePath, getCachedToken, isExpired, setCachedToken, CacheEntry } from './token-cache';
|
|
5
|
-
|
|
6
|
-
jest.mock('fs');
|
|
7
|
-
jest.mock('os');
|
|
8
|
-
|
|
9
|
-
const mockedFs = jest.mocked(fs);
|
|
10
|
-
const mockedOs = jest.mocked(os);
|
|
11
|
-
|
|
12
|
-
const FAKE_HOME = '/home/testuser';
|
|
13
|
-
const CACHE_PATH = path.join(FAKE_HOME, '.nx-adsp', 'token-cache.json');
|
|
14
|
-
|
|
15
|
-
const ACCESS_URL = 'https://access.example.com/auth';
|
|
16
|
-
const REALM = 'my-tenant';
|
|
17
|
-
|
|
18
|
-
function futureIso(secondsFromNow: number): string {
|
|
19
|
-
return new Date(Date.now() + secondsFromNow * 1000).toISOString();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function pastIso(secondsAgo: number): string {
|
|
23
|
-
return new Date(Date.now() - secondsAgo * 1000).toISOString();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe('getCacheFilePath', () => {
|
|
27
|
-
it('returns path under home directory', () => {
|
|
28
|
-
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
29
|
-
expect(getCacheFilePath()).toBe(CACHE_PATH);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('isExpired', () => {
|
|
34
|
-
it('returns false when token expires in the future (beyond buffer)', () => {
|
|
35
|
-
const entry: CacheEntry = { accessToken: 'tok', expiresAt: futureIso(120) };
|
|
36
|
-
expect(isExpired(entry)).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('returns true when token is past its expiry', () => {
|
|
40
|
-
const entry: CacheEntry = { accessToken: 'tok', expiresAt: pastIso(10) };
|
|
41
|
-
expect(isExpired(entry)).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('returns true when token expires within the 60-second buffer', () => {
|
|
45
|
-
const entry: CacheEntry = { accessToken: 'tok', expiresAt: futureIso(30) };
|
|
46
|
-
expect(isExpired(entry)).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('getCachedToken', () => {
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('returns null when cache file does not exist', () => {
|
|
56
|
-
mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
|
|
57
|
-
expect(getCachedToken(ACCESS_URL, REALM)).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('returns null when realm is not in cache', () => {
|
|
61
|
-
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ 'other::realm': { accessToken: 'x', expiresAt: futureIso(3600) } }));
|
|
62
|
-
expect(getCachedToken(ACCESS_URL, REALM)).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('returns the cached entry when present', () => {
|
|
66
|
-
const entry: CacheEntry = { accessToken: 'abc', refreshToken: 'ref', expiresAt: futureIso(3600) };
|
|
67
|
-
const key = `${ACCESS_URL}::${REALM}`;
|
|
68
|
-
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ [key]: entry }));
|
|
69
|
-
|
|
70
|
-
expect(getCachedToken(ACCESS_URL, REALM)).toEqual(entry);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('setCachedToken', () => {
|
|
75
|
-
beforeEach(() => {
|
|
76
|
-
mockedOs.homedir.mockReturnValue(FAKE_HOME);
|
|
77
|
-
mockedFs.existsSync.mockReturnValue(true);
|
|
78
|
-
mockedFs.readFileSync.mockImplementation(() => { throw new Error('ENOENT'); });
|
|
79
|
-
(mockedFs.writeFileSync as jest.Mock).mockReset();
|
|
80
|
-
(mockedFs.mkdirSync as jest.Mock).mockReset();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('writes a new entry with correct fields', () => {
|
|
84
|
-
setCachedToken(ACCESS_URL, REALM, 'access-tok', 'refresh-tok', 3600);
|
|
85
|
-
|
|
86
|
-
expect(mockedFs.writeFileSync).toHaveBeenCalledWith(
|
|
87
|
-
CACHE_PATH,
|
|
88
|
-
expect.stringContaining('"accessToken": "access-tok"'),
|
|
89
|
-
expect.objectContaining({ mode: 0o600 })
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const written = JSON.parse((mockedFs.writeFileSync as jest.Mock).mock.calls[0][1] as string);
|
|
93
|
-
const entry = written[`${ACCESS_URL}::${REALM}`];
|
|
94
|
-
expect(entry.accessToken).toBe('access-tok');
|
|
95
|
-
expect(entry.refreshToken).toBe('refresh-tok');
|
|
96
|
-
expect(new Date(entry.expiresAt).getTime()).toBeGreaterThan(Date.now() + 3590 * 1000);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('writes without refreshToken when not provided', () => {
|
|
100
|
-
setCachedToken(ACCESS_URL, REALM, 'access-tok', undefined, 3600);
|
|
101
|
-
|
|
102
|
-
const written = JSON.parse((mockedFs.writeFileSync as jest.Mock).mock.calls[0][1] as string);
|
|
103
|
-
const entry = written[`${ACCESS_URL}::${REALM}`];
|
|
104
|
-
expect(entry.refreshToken).toBeUndefined();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('creates the cache directory if it does not exist', () => {
|
|
108
|
-
mockedFs.existsSync.mockReturnValue(false);
|
|
109
|
-
|
|
110
|
-
setCachedToken(ACCESS_URL, REALM, 'tok', undefined, 300);
|
|
111
|
-
|
|
112
|
-
expect(mockedFs.mkdirSync).toHaveBeenCalledWith(
|
|
113
|
-
path.dirname(CACHE_PATH),
|
|
114
|
-
expect.objectContaining({ recursive: true, mode: 0o700 })
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
});
|