ag-awsauth 0.0.290 → 0.0.292
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 +39 -2
- package/dist/config.d.ts +0 -1
- package/dist/config.js +2 -3
- package/dist/helpers/awsconfig.js +20 -0
- package/dist/helpers/browser.d.ts +1 -0
- package/dist/helpers/browser.js +25 -21
- package/dist/helpers/oidc.d.ts +1 -0
- package/dist/helpers/oidc.js +27 -9
- package/dist/helpers/oidcRefresh.d.ts +6 -0
- package/dist/helpers/oidcRefresh.js +55 -0
- package/dist/helpers/sso.d.ts +2 -7
- package/dist/helpers/sso.js +62 -39
- package/dist/helpers/sts.d.ts +1 -13
- package/dist/helpers/sts.js +0 -30
- package/dist/index.js +23 -5
- package/dist/types.d.ts +10 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,8 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## Usage
|
|
4
4
|
|
|
5
|
-
npm i -g awsauth
|
|
5
|
+
npm i -g ag-awsauth
|
|
6
6
|
|
|
7
7
|
ag-awsauth -c
|
|
8
8
|
|
|
9
|
-
ag-awsauth
|
|
9
|
+
ag-awsauth
|
|
10
|
+
|
|
11
|
+
## Session duration
|
|
12
|
+
|
|
13
|
+
- SAML role sessions request 8 hours. The IAM role maximum session duration and
|
|
14
|
+
the SAML assertion can shorten this.
|
|
15
|
+
- Native IAM Identity Center role sessions use the duration configured on the
|
|
16
|
+
selected permission set. Set that permission set to 8 hours in IAM Identity
|
|
17
|
+
Center; the API does not accept a per-request duration.
|
|
18
|
+
- OIDC access tokens are refreshed automatically after the next interactive
|
|
19
|
+
sign-in. The refresh token and client registration are stored with the other
|
|
20
|
+
session details in the AWS credentials file.
|
|
21
|
+
- The AWS access portal has a separate user interactive session duration. Its
|
|
22
|
+
default is 8 hours, but an administrator can change it under IAM Identity
|
|
23
|
+
Center **Settings > Authentication > Session duration**. An external IdP or
|
|
24
|
+
Active Directory policy can impose a shorter duration.
|
|
25
|
+
|
|
26
|
+
## Repeated sign-in prompts
|
|
27
|
+
|
|
28
|
+
Expired AWS role credentials do not necessarily mean the SSO session has
|
|
29
|
+
expired. A valid portal session can renew SAML role credentials directly;
|
|
30
|
+
native AWS accounts use the OIDC access token, refreshing it when AWS rejects
|
|
31
|
+
it. Rotated refresh tokens are saved immediately, even if a later request fails.
|
|
32
|
+
|
|
33
|
+
When AWS rejects the refresh token (`InvalidGrantException`), another sign-in
|
|
34
|
+
is required. An unexpired client registration alone does not make a refresh
|
|
35
|
+
token valid. The warning before the MFA prompt reports the reason. Portal
|
|
36
|
+
outages and network failures are reported as errors instead of requesting login.
|
|
37
|
+
|
|
38
|
+
Changes in a source checkout do not update a separately installed global
|
|
39
|
+
`ag-awsauth`. On PowerShell, `(Get-Command ag-awsauth).Source` shows which
|
|
40
|
+
installation the terminal runs.
|
|
41
|
+
|
|
42
|
+
## Tests
|
|
43
|
+
|
|
44
|
+
Run `npm test`. Returning-user regressions run the real application entry point
|
|
45
|
+
in separate processes with temporary AWS files, simulated external AWS APIs,
|
|
46
|
+
and a prompt trap. They never contact AWS or use real credentials.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare const logPath = "log.txt";
|
|
2
2
|
export declare const stsDurationSeconds: number;
|
|
3
|
-
export declare const nativeStsDurationSeconds: number;
|
|
4
3
|
export declare let basePath: string;
|
|
5
4
|
export declare const setBasePath: (bp: string) => void;
|
|
6
5
|
export declare let identityCenterRegion: string;
|
package/dist/config.js
CHANGED
|
@@ -3,13 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.timeoutMedMs = exports.timeoutShortMs = exports.timeoutMs = exports.runConfig = exports.setConfig = exports.validateConfig = exports.targetRegion = exports.ssoStartUrl = exports.identityCenterRegion = exports.setBasePath = exports.basePath = exports.
|
|
6
|
+
exports.timeoutMedMs = exports.timeoutShortMs = exports.timeoutMs = exports.runConfig = exports.setConfig = exports.validateConfig = exports.targetRegion = exports.ssoStartUrl = exports.identityCenterRegion = exports.setBasePath = exports.basePath = exports.stsDurationSeconds = exports.logPath = void 0;
|
|
7
7
|
const envfile_1 = require("envfile");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
exports.logPath = 'log.txt';
|
|
11
|
-
exports.stsDurationSeconds = 60 * 60 *
|
|
12
|
-
exports.nativeStsDurationSeconds = 60 * 60 * 1; //1h
|
|
11
|
+
exports.stsDurationSeconds = 60 * 60 * 8;
|
|
13
12
|
exports.basePath = '';
|
|
14
13
|
const setBasePath = (bp) => {
|
|
15
14
|
exports.basePath = bp;
|
|
@@ -44,6 +44,26 @@ const updateAwsCredentials = (p) => __awaiter(void 0, void 0, void 0, function*
|
|
|
44
44
|
creds.default.aws_session_token = p.sessionToken;
|
|
45
45
|
creds.default.aws_access_token = p.accessToken;
|
|
46
46
|
creds.default.aws_sso_authn = p.ssoAuthn;
|
|
47
|
+
if (p.refreshToken &&
|
|
48
|
+
p.clientId &&
|
|
49
|
+
p.clientSecret &&
|
|
50
|
+
p.clientSecretExpiresAt &&
|
|
51
|
+
p.accessTokenExpiresAt) {
|
|
52
|
+
creds.default.aws_sso_refresh_token = p.refreshToken;
|
|
53
|
+
creds.default.aws_sso_client_id = p.clientId;
|
|
54
|
+
creds.default.aws_sso_client_secret = p.clientSecret;
|
|
55
|
+
creds.default.aws_sso_client_secret_expires_at =
|
|
56
|
+
p.clientSecretExpiresAt.toString();
|
|
57
|
+
creds.default.aws_sso_access_token_expires_at =
|
|
58
|
+
p.accessTokenExpiresAt.toString();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
delete creds.default.aws_sso_refresh_token;
|
|
62
|
+
delete creds.default.aws_sso_client_id;
|
|
63
|
+
delete creds.default.aws_sso_client_secret;
|
|
64
|
+
delete creds.default.aws_sso_client_secret_expires_at;
|
|
65
|
+
delete creds.default.aws_sso_access_token_expires_at;
|
|
66
|
+
}
|
|
47
67
|
}
|
|
48
68
|
const newcreds = (0, ini_1.stringify)(creds);
|
|
49
69
|
(0, log_1.info)(`saving updated default creds to .aws/credentials`);
|
|
@@ -2,6 +2,7 @@ import type { Page } from 'puppeteer';
|
|
|
2
2
|
export declare const closeBrowser: () => Promise<void>;
|
|
3
3
|
export declare const launchBrowser: () => Promise<void>;
|
|
4
4
|
export declare const goToPage: (url: string) => Promise<Page>;
|
|
5
|
+
export declare const allowAccessIfRequested: (page: Page) => Promise<void>;
|
|
5
6
|
export declare function getMFA(p: {
|
|
6
7
|
verificationUriComplete: string;
|
|
7
8
|
creds: {
|
package/dist/helpers/browser.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.goToPage = exports.launchBrowser = exports.closeBrowser = void 0;
|
|
12
|
+
exports.allowAccessIfRequested = exports.goToPage = exports.launchBrowser = exports.closeBrowser = void 0;
|
|
13
13
|
exports.getMFA = getMFA;
|
|
14
14
|
const log_1 = require("ag-common/dist/common/helpers/log");
|
|
15
15
|
const puppeteer_1 = require("puppeteer");
|
|
@@ -83,6 +83,15 @@ const goToPage = (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
exports.goToPage = goToPage;
|
|
86
|
+
const allowAccessIfRequested = (page) => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
|
+
const allowAccess = yield page.waitForSelector('[data-testid="allow-access-button"]', { timeout: config_1.timeoutMs, visible: true });
|
|
88
|
+
if (!allowAccess) {
|
|
89
|
+
throw new Error('allow access button not found');
|
|
90
|
+
}
|
|
91
|
+
(0, log_1.info)('click allow-access-button');
|
|
92
|
+
yield allowAccess.click();
|
|
93
|
+
});
|
|
94
|
+
exports.allowAccessIfRequested = allowAccessIfRequested;
|
|
86
95
|
function getMFA(p) {
|
|
87
96
|
return __awaiter(this, void 0, void 0, function* () {
|
|
88
97
|
var _a;
|
|
@@ -208,33 +217,28 @@ function getMFA(p) {
|
|
|
208
217
|
catch (e) {
|
|
209
218
|
(0, log_1.debug)('error accepting user code:', e);
|
|
210
219
|
}
|
|
220
|
+
let accessStateFound = false;
|
|
211
221
|
try {
|
|
212
222
|
(0, log_1.info)('allow-access-button');
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
if (messageDiv) {
|
|
217
|
-
(0, log_1.info)('click allow-access-button');
|
|
218
|
-
yield messageDiv.click();
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
throw new Error('access prompt not found');
|
|
222
|
-
}
|
|
223
|
+
yield (0, exports.allowAccessIfRequested)(page);
|
|
224
|
+
accessStateFound = true;
|
|
223
225
|
}
|
|
224
226
|
catch (e) {
|
|
225
227
|
(0, log_1.debug)('error clicking allow-access-button:', e);
|
|
226
228
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
(
|
|
233
|
-
|
|
229
|
+
if (!accessStateFound) {
|
|
230
|
+
try {
|
|
231
|
+
const verif = yield page.waitForSelector('#cli_verification_btn', {
|
|
232
|
+
timeout: config_1.timeoutShortMs,
|
|
233
|
+
});
|
|
234
|
+
if (verif) {
|
|
235
|
+
(0, log_1.info)('clicking auth request button');
|
|
236
|
+
yield verif.click();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
(0, log_1.debug)('error clicking auth request button:', e);
|
|
234
241
|
}
|
|
235
|
-
}
|
|
236
|
-
catch (e) {
|
|
237
|
-
(0, log_1.debug)('error clicking auth request button:', e);
|
|
238
242
|
}
|
|
239
243
|
//
|
|
240
244
|
(0, log_1.info)('waiting for success');
|
package/dist/helpers/oidc.d.ts
CHANGED
package/dist/helpers/oidc.js
CHANGED
|
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.refreshAccessToken = void 0;
|
|
12
13
|
exports.requestMFA = requestMFA;
|
|
13
14
|
/* eslint-disable no-await-in-loop */
|
|
14
15
|
const client_sso_oidc_1 = require("@aws-sdk/client-sso-oidc");
|
|
@@ -18,6 +19,9 @@ const config_1 = require("../config");
|
|
|
18
19
|
const browser_1 = require("./browser");
|
|
19
20
|
const input_1 = require("./input");
|
|
20
21
|
const version_1 = require("./version");
|
|
22
|
+
var oidcRefresh_1 = require("./oidcRefresh");
|
|
23
|
+
Object.defineProperty(exports, "refreshAccessToken", { enumerable: true, get: function () { return oidcRefresh_1.refreshAccessToken; } });
|
|
24
|
+
const deviceCodeGrant = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
21
25
|
function requestMFA(p) {
|
|
22
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
27
|
const sso_oidc = new client_sso_oidc_1.SSOOIDCClient({ region: p.identityCenterRegion });
|
|
@@ -27,7 +31,12 @@ function requestMFA(p) {
|
|
|
27
31
|
const rcc = yield sso_oidc.send(new client_sso_oidc_1.RegisterClientCommand({
|
|
28
32
|
clientName: creds.username,
|
|
29
33
|
clientType: 'public',
|
|
34
|
+
grantTypes: [deviceCodeGrant, 'refresh_token'],
|
|
35
|
+
scopes: ['sso:account:access'],
|
|
30
36
|
}));
|
|
37
|
+
if (!rcc.clientId || !rcc.clientSecret || !rcc.clientSecretExpiresAt) {
|
|
38
|
+
throw new Error('OIDC client registration details are missing');
|
|
39
|
+
}
|
|
31
40
|
const sda = yield sso_oidc.send(new client_sso_oidc_1.StartDeviceAuthorizationCommand({
|
|
32
41
|
clientId: rcc.clientId,
|
|
33
42
|
clientSecret: rcc.clientSecret,
|
|
@@ -54,12 +63,28 @@ function requestMFA(p) {
|
|
|
54
63
|
clientSecret: rcc.clientSecret,
|
|
55
64
|
code: sda.userCode,
|
|
56
65
|
deviceCode: sda.deviceCode,
|
|
57
|
-
grantType:
|
|
66
|
+
grantType: deviceCodeGrant,
|
|
58
67
|
}));
|
|
59
68
|
accessToken = ctc.accessToken;
|
|
60
69
|
if (!accessToken) {
|
|
61
70
|
throw new Error('no access token');
|
|
62
71
|
}
|
|
72
|
+
if (!ctc.refreshToken || !ctc.expiresIn) {
|
|
73
|
+
throw new Error('OIDC refresh details are missing');
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
accessToken,
|
|
77
|
+
ssoAuthn,
|
|
78
|
+
refreshToken: ctc.refreshToken,
|
|
79
|
+
clientId: rcc.clientId,
|
|
80
|
+
clientSecret: rcc.clientSecret,
|
|
81
|
+
clientSecretExpiresAt: rcc.clientSecretExpiresAt,
|
|
82
|
+
accessTokenExpiresAt: Date.now() + ctc.expiresIn * 1000,
|
|
83
|
+
region: config_1.identityCenterRegion,
|
|
84
|
+
accessKeyId: '',
|
|
85
|
+
secretAccessKey: '',
|
|
86
|
+
sessionToken: '',
|
|
87
|
+
};
|
|
63
88
|
}
|
|
64
89
|
catch (e) {
|
|
65
90
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -71,13 +96,6 @@ function requestMFA(p) {
|
|
|
71
96
|
yield (0, sleep_1.sleep)(5000);
|
|
72
97
|
}
|
|
73
98
|
} while (!accessToken);
|
|
74
|
-
|
|
75
|
-
accessToken,
|
|
76
|
-
ssoAuthn,
|
|
77
|
-
region: config_1.identityCenterRegion,
|
|
78
|
-
accessKeyId: '',
|
|
79
|
-
secretAccessKey: '',
|
|
80
|
-
sessionToken: '',
|
|
81
|
-
};
|
|
99
|
+
throw new Error('no access token');
|
|
82
100
|
});
|
|
83
101
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IAwsCreds } from '../types';
|
|
2
|
+
export declare class InteractiveSignInRequiredError extends Error {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function requiresInteractiveSignIn(e: unknown): boolean;
|
|
6
|
+
export declare function refreshAccessToken(p: IAwsCreds): Promise<IAwsCreds>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.InteractiveSignInRequiredError = void 0;
|
|
13
|
+
exports.requiresInteractiveSignIn = requiresInteractiveSignIn;
|
|
14
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
15
|
+
const client_sso_oidc_1 = require("@aws-sdk/client-sso-oidc");
|
|
16
|
+
const config_1 = require("../config");
|
|
17
|
+
class InteractiveSignInRequiredError extends Error {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.name = 'InteractiveSignInRequiredError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.InteractiveSignInRequiredError = InteractiveSignInRequiredError;
|
|
24
|
+
function requiresInteractiveSignIn(e) {
|
|
25
|
+
return (e instanceof Error &&
|
|
26
|
+
[
|
|
27
|
+
'InteractiveSignInRequiredError',
|
|
28
|
+
'InvalidGrantException',
|
|
29
|
+
'InvalidClientException',
|
|
30
|
+
'UnauthorizedClientException',
|
|
31
|
+
].includes(e.name));
|
|
32
|
+
}
|
|
33
|
+
function refreshAccessToken(p) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
if (!p.refreshToken || !p.clientId || !p.clientSecret) {
|
|
36
|
+
throw new InteractiveSignInRequiredError('OIDC refresh details are missing');
|
|
37
|
+
}
|
|
38
|
+
if (p.clientSecretExpiresAt &&
|
|
39
|
+
p.clientSecretExpiresAt <= Math.floor(Date.now() / 1000)) {
|
|
40
|
+
throw new InteractiveSignInRequiredError('OIDC client registration has expired');
|
|
41
|
+
}
|
|
42
|
+
const response = yield new client_sso_oidc_1.SSOOIDCClient({
|
|
43
|
+
region: config_1.identityCenterRegion,
|
|
44
|
+
}).send(new client_sso_oidc_1.CreateTokenCommand({
|
|
45
|
+
clientId: p.clientId,
|
|
46
|
+
clientSecret: p.clientSecret,
|
|
47
|
+
grantType: 'refresh_token',
|
|
48
|
+
refreshToken: p.refreshToken,
|
|
49
|
+
}));
|
|
50
|
+
if (!response.accessToken || !response.expiresIn) {
|
|
51
|
+
throw new Error('OIDC refresh did not return an access token');
|
|
52
|
+
}
|
|
53
|
+
return Object.assign(Object.assign({}, p), { accessToken: response.accessToken, refreshToken: response.refreshToken || p.refreshToken, accessTokenExpiresAt: Date.now() + response.expiresIn * 1000 });
|
|
54
|
+
});
|
|
55
|
+
}
|
package/dist/helpers/sso.d.ts
CHANGED
|
@@ -6,13 +6,8 @@ export declare const getAssumedRole: (p: {
|
|
|
6
6
|
accountId: string;
|
|
7
7
|
roleName: string;
|
|
8
8
|
}>;
|
|
9
|
-
export declare const getOIDCCredentialsFromAccessToken: (p:
|
|
10
|
-
|
|
11
|
-
ssoAuthn: string;
|
|
12
|
-
}) => Promise<IAwsCreds>;
|
|
13
|
-
export declare function appInstances(p: {
|
|
14
|
-
ssoAuthn: string;
|
|
15
|
-
}): Promise<IAppInstance[]>;
|
|
9
|
+
export declare const getOIDCCredentialsFromAccessToken: (p: IAwsCreds, accountId?: string) => Promise<IAwsCreds>;
|
|
10
|
+
export declare function appInstances(p: Pick<IAwsCreds, 'accessToken' | 'ssoAuthn'>): Promise<IAppInstance[]>;
|
|
16
11
|
export declare function getSamlAssertion(p: IAwsCreds, instance: IAppInstance): Promise<{
|
|
17
12
|
samlAssertion: string;
|
|
18
13
|
providerArn: string;
|
package/dist/helpers/sso.js
CHANGED
|
@@ -15,13 +15,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.tryExistingCredentials = exports.getOIDCCredentialsFromAccessToken = exports.getAssumedRole = void 0;
|
|
16
16
|
exports.appInstances = appInstances;
|
|
17
17
|
exports.getSamlAssertion = getSamlAssertion;
|
|
18
|
+
/* eslint-disable no-await-in-loop */
|
|
18
19
|
const client_sso_1 = require("@aws-sdk/client-sso");
|
|
19
20
|
const log_1 = require("ag-common/dist/common/helpers/log");
|
|
20
21
|
const string_1 = require("ag-common/dist/common/helpers/string");
|
|
21
22
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
22
23
|
const config_1 = require("../config");
|
|
23
24
|
const awsconfig_1 = require("./awsconfig");
|
|
24
|
-
const
|
|
25
|
+
const oidcRefresh_1 = require("./oidcRefresh");
|
|
26
|
+
const portalTokens = (p) => [...new Set([p.ssoAuthn, p.accessToken])].filter(Boolean);
|
|
27
|
+
function fetchPortalJson(url, p, isValid) {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
// Each token is tried in order because portal and OIDC sessions can expire independently.
|
|
30
|
+
for (const token of portalTokens(p)) {
|
|
31
|
+
const response = yield (0, node_fetch_1.default)(url, {
|
|
32
|
+
headers: { 'x-amz-sso_bearer_token': token },
|
|
33
|
+
});
|
|
34
|
+
if (response.status === 401) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (response.status >= 400) {
|
|
38
|
+
throw new Error(`AWS portal request failed (HTTP ${response.status})`);
|
|
39
|
+
}
|
|
40
|
+
const body = (yield response.json());
|
|
41
|
+
if (!isValid(body)) {
|
|
42
|
+
throw new Error('AWS portal returned an unexpected response');
|
|
43
|
+
}
|
|
44
|
+
return body;
|
|
45
|
+
}
|
|
46
|
+
throw new oidcRefresh_1.InteractiveSignInRequiredError('Cached portal tokens were rejected');
|
|
47
|
+
});
|
|
48
|
+
}
|
|
25
49
|
const getAssumedRole = (p) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
50
|
var _a, _b, _c, _d;
|
|
27
51
|
const sso = new client_sso_1.SSOClient({ region: config_1.identityCenterRegion });
|
|
@@ -51,9 +75,9 @@ const getAssumedRole = (p) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
51
75
|
return role;
|
|
52
76
|
});
|
|
53
77
|
exports.getAssumedRole = getAssumedRole;
|
|
54
|
-
const getOIDCCredentialsFromAccessToken = (p) => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
+
const getOIDCCredentialsFromAccessToken = (p, accountId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
79
|
const sso = new client_sso_1.SSOClient({ region: config_1.identityCenterRegion });
|
|
56
|
-
const role = yield (0, exports.getAssumedRole)({ accessToken: p.accessToken });
|
|
80
|
+
const role = yield (0, exports.getAssumedRole)({ accessToken: p.accessToken, accountId });
|
|
57
81
|
const ssoResp = yield sso.send(new client_sso_1.GetRoleCredentialsCommand(Object.assign(Object.assign({}, role), { accessToken: p.accessToken })));
|
|
58
82
|
const rc = ssoResp.roleCredentials;
|
|
59
83
|
if (!(rc === null || rc === void 0 ? void 0 : rc.accessKeyId) ||
|
|
@@ -67,25 +91,19 @@ const getOIDCCredentialsFromAccessToken = (p) => __awaiter(void 0, void 0, void
|
|
|
67
91
|
exports.getOIDCCredentialsFromAccessToken = getOIDCCredentialsFromAccessToken;
|
|
68
92
|
function appInstances(p) {
|
|
69
93
|
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
-
const ai =
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
72
|
-
if (!ai.result) {
|
|
73
|
-
throw new Error('appinstance error' + JSON.stringify(ai, null, 2));
|
|
74
|
-
}
|
|
94
|
+
const ai = yield fetchPortalJson(`https://portal.sso.${config_1.identityCenterRegion}.amazonaws.com/instance/appinstances`, p, (body) => Boolean(body.result));
|
|
75
95
|
return ai.result.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
76
96
|
});
|
|
77
97
|
}
|
|
78
98
|
function getSamlAssertion(p, instance) {
|
|
79
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
80
100
|
var _a;
|
|
81
|
-
const det =
|
|
101
|
+
const det = yield fetchPortalJson(`https://portal.sso.${config_1.identityCenterRegion}.amazonaws.com/instance/appinstance/${instance.id}/profiles`, p, (body) => { var _a; return Boolean((_a = body.result[0]) === null || _a === void 0 ? void 0 : _a.url); });
|
|
82
102
|
const asserturl = (_a = det.result[0]) === null || _a === void 0 ? void 0 : _a.url;
|
|
83
103
|
if (!asserturl) {
|
|
84
104
|
throw new Error('assertion url cant be found');
|
|
85
105
|
}
|
|
86
|
-
const assertion =
|
|
87
|
-
headers: { 'x-amz-sso_bearer_token': p.ssoAuthn },
|
|
88
|
-
})).json());
|
|
106
|
+
const assertion = yield fetchPortalJson(asserturl, p, (body) => Boolean(body.encodedResponse));
|
|
89
107
|
const decoded = (0, string_1.fromBase64)(assertion.encodedResponse);
|
|
90
108
|
const res = new RegExp(/<saml2:AttributeValue xmlns:xsi="http:\/\/www.w3.org\/2001\/XMLSchema-instance" xsi:type="xsd:string">(arn.*?)</gim).exec(decoded);
|
|
91
109
|
if (!(res === null || res === void 0 ? void 0 : res[1])) {
|
|
@@ -96,7 +114,6 @@ function getSamlAssertion(p, instance) {
|
|
|
96
114
|
});
|
|
97
115
|
}
|
|
98
116
|
const tryExistingCredentials = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
99
|
-
var _a;
|
|
100
117
|
const credraw = yield (0, awsconfig_1.getAwsCredentials)();
|
|
101
118
|
if (!credraw.default.aws_access_token) {
|
|
102
119
|
return undefined;
|
|
@@ -107,40 +124,46 @@ const tryExistingCredentials = () => __awaiter(void 0, void 0, void 0, function*
|
|
|
107
124
|
sessionToken: credraw.default.aws_session_token,
|
|
108
125
|
accessToken: credraw.default.aws_access_token,
|
|
109
126
|
ssoAuthn: credraw.default.aws_sso_authn,
|
|
127
|
+
refreshToken: credraw.default.aws_sso_refresh_token,
|
|
128
|
+
clientId: credraw.default.aws_sso_client_id,
|
|
129
|
+
clientSecret: credraw.default.aws_sso_client_secret,
|
|
130
|
+
clientSecretExpiresAt: Number(credraw.default.aws_sso_client_secret_expires_at) || undefined,
|
|
131
|
+
accessTokenExpiresAt: Number(credraw.default.aws_sso_access_token_expires_at) || undefined,
|
|
110
132
|
region: config_1.identityCenterRegion,
|
|
111
133
|
};
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
const refresh = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
credentials = yield (0, oidcRefresh_1.refreshAccessToken)(credentials);
|
|
136
|
+
yield (0, awsconfig_1.updateAwsCredentials)(credentials);
|
|
137
|
+
});
|
|
138
|
+
try {
|
|
139
|
+
yield appInstances(credentials);
|
|
115
140
|
}
|
|
116
|
-
|
|
141
|
+
catch (e) {
|
|
142
|
+
if (!(0, oidcRefresh_1.requiresInteractiveSignIn)(e)) {
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
117
145
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
accessToken: credraw.default.aws_access_token,
|
|
121
|
-
ssoAuthn: credraw.default.aws_sso_authn,
|
|
122
|
-
});
|
|
123
|
-
return credentials;
|
|
146
|
+
yield refresh();
|
|
147
|
+
yield appInstances(credentials);
|
|
124
148
|
}
|
|
125
149
|
catch (e) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
129
|
-
if ((_a = em.includes('is expired')) !== null && _a !== void 0 ? _a : em.includes('or invalid')) {
|
|
130
|
-
(0, log_1.info)('access token or sso expired, need to wipe');
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
(0, log_1.info)(e);
|
|
150
|
+
if (!(0, oidcRefresh_1.requiresInteractiveSignIn)(e)) {
|
|
151
|
+
throw e;
|
|
134
152
|
}
|
|
153
|
+
(0, log_1.warn)(`Unable to reuse the cached SSO session (${e instanceof Error ? e.name : 'unknown error'}); interactive sign-in required.`);
|
|
154
|
+
return emptyCredentials();
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
region: config_1.identityCenterRegion,
|
|
141
|
-
accessKeyId: '',
|
|
142
|
-
secretAccessKey: '',
|
|
143
|
-
sessionToken: '',
|
|
144
|
-
};
|
|
157
|
+
// Role credentials and portal sessions expire independently. Renew the role
|
|
158
|
+
// only after the user selects an application; SAML does not need OIDC.
|
|
159
|
+
return credentials;
|
|
145
160
|
});
|
|
146
161
|
exports.tryExistingCredentials = tryExistingCredentials;
|
|
162
|
+
const emptyCredentials = () => ({
|
|
163
|
+
accessToken: '',
|
|
164
|
+
ssoAuthn: '',
|
|
165
|
+
region: config_1.identityCenterRegion,
|
|
166
|
+
accessKeyId: '',
|
|
167
|
+
secretAccessKey: '',
|
|
168
|
+
sessionToken: '',
|
|
169
|
+
});
|
package/dist/helpers/sts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IAwsCreds
|
|
1
|
+
import type { IAwsCreds } from '../types';
|
|
2
2
|
export declare function validateCredentials(credentials: IAwsCreds): Promise<{
|
|
3
3
|
accountId: string;
|
|
4
4
|
principalArn: string;
|
|
@@ -10,15 +10,3 @@ export declare function getApplicationCreds(p: {
|
|
|
10
10
|
providerArn: string;
|
|
11
11
|
roleArn: string;
|
|
12
12
|
}): Promise<IAwsCreds>;
|
|
13
|
-
export declare function directStsAssume(p: {
|
|
14
|
-
credentials: IAwsCreds;
|
|
15
|
-
targetRegion: string;
|
|
16
|
-
metadata: SearchMetadata;
|
|
17
|
-
}): Promise<{
|
|
18
|
-
region: string;
|
|
19
|
-
accessKeyId: string;
|
|
20
|
-
secretAccessKey: string;
|
|
21
|
-
sessionToken: string;
|
|
22
|
-
accessToken: string;
|
|
23
|
-
ssoAuthn: string;
|
|
24
|
-
}>;
|
package/dist/helpers/sts.js
CHANGED
|
@@ -11,11 +11,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.validateCredentials = validateCredentials;
|
|
13
13
|
exports.getApplicationCreds = getApplicationCreds;
|
|
14
|
-
exports.directStsAssume = directStsAssume;
|
|
15
14
|
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
16
15
|
const log_1 = require("ag-common/dist/common/helpers/log");
|
|
17
16
|
const config_1 = require("../config");
|
|
18
|
-
const sso_1 = require("./sso");
|
|
19
17
|
function validateCredentials(credentials) {
|
|
20
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
19
|
var _a;
|
|
@@ -25,7 +23,6 @@ function validateCredentials(credentials) {
|
|
|
25
23
|
});
|
|
26
24
|
try {
|
|
27
25
|
const stub = yield sts.getCallerIdentity({});
|
|
28
|
-
yield (0, sso_1.appInstances)(credentials);
|
|
29
26
|
if (((_a = stub.$metadata.httpStatusCode) !== null && _a !== void 0 ? _a : 500) < 400 &&
|
|
30
27
|
stub.Account &&
|
|
31
28
|
stub.Arn) {
|
|
@@ -68,30 +65,3 @@ function getApplicationCreds(p) {
|
|
|
68
65
|
return Object.assign(Object.assign({}, p.originCreds), { region: p.targetRegion, accessKeyId: ret.Credentials.AccessKeyId, secretAccessKey: ret.Credentials.SecretAccessKey, sessionToken: ret.Credentials.SessionToken });
|
|
69
66
|
});
|
|
70
67
|
}
|
|
71
|
-
function directStsAssume(p) {
|
|
72
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
var _a, _b;
|
|
74
|
-
const role = yield (0, sso_1.getAssumedRole)({
|
|
75
|
-
accessToken: p.credentials.accessToken,
|
|
76
|
-
accountId: p.metadata.AccountId,
|
|
77
|
-
});
|
|
78
|
-
const sts = new client_sts_1.STS({
|
|
79
|
-
credentials: p.credentials,
|
|
80
|
-
region: p.targetRegion,
|
|
81
|
-
});
|
|
82
|
-
const ar = yield sts.assumeRole({
|
|
83
|
-
RoleArn: `arn:aws:iam::${role.accountId}:role/${role.roleName}`,
|
|
84
|
-
RoleSessionName: 'awsauth',
|
|
85
|
-
DurationSeconds: config_1.nativeStsDurationSeconds,
|
|
86
|
-
});
|
|
87
|
-
if (((_a = ar.$metadata.httpStatusCode) !== null && _a !== void 0 ? _a : 500) >= 400) {
|
|
88
|
-
throw new Error('assume role error' + JSON.stringify(ar, null, 2));
|
|
89
|
-
}
|
|
90
|
-
if (!((_b = ar.Credentials) === null || _b === void 0 ? void 0 : _b.AccessKeyId) ||
|
|
91
|
-
!ar.Credentials.SecretAccessKey ||
|
|
92
|
-
!ar.Credentials.SessionToken) {
|
|
93
|
-
throw new Error('no creds');
|
|
94
|
-
}
|
|
95
|
-
return Object.assign(Object.assign({}, p.credentials), { region: p.targetRegion, accessKeyId: ar.Credentials.AccessKeyId, secretAccessKey: ar.Credentials.SecretAccessKey, sessionToken: ar.Credentials.SessionToken });
|
|
96
|
-
});
|
|
97
|
-
}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const config_1 = require("./config");
|
|
|
23
23
|
const awsconfig_1 = require("./helpers/awsconfig");
|
|
24
24
|
const input_1 = require("./helpers/input");
|
|
25
25
|
const oidc_1 = require("./helpers/oidc");
|
|
26
|
+
const oidcRefresh_1 = require("./helpers/oidcRefresh");
|
|
26
27
|
const sso_1 = require("./helpers/sso");
|
|
27
28
|
const sts_1 = require("./helpers/sts");
|
|
28
29
|
const version_1 = require("./helpers/version");
|
|
@@ -85,11 +86,28 @@ function main(args) {
|
|
|
85
86
|
let debugRole = '';
|
|
86
87
|
if (instance.searchMetadata) {
|
|
87
88
|
(0, log_1.info)('account is native aws, directly connecting');
|
|
88
|
-
|
|
89
|
-
credentials,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
try {
|
|
90
|
+
credentials = yield (0, sso_1.getOIDCCredentialsFromAccessToken)(credentials, instance.searchMetadata.AccountId);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
if (!(e instanceof Error) || e.name !== 'UnauthorizedException') {
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
credentials = yield (0, oidcRefresh_1.refreshAccessToken)(credentials);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
if (!(0, oidcRefresh_1.requiresInteractiveSignIn)(e)) {
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
(0, log_1.warn)(`Unable to reuse the cached SSO session (${e instanceof Error ? e.name : 'unknown error'}); interactive sign-in required.`);
|
|
104
|
+
credentials = yield (0, oidc_1.requestMFA)({ identityCenterRegion: config_1.identityCenterRegion, ssoStartUrl: config_1.ssoStartUrl });
|
|
105
|
+
}
|
|
106
|
+
// Save rotated tokens before another AWS request can fail.
|
|
107
|
+
yield (0, awsconfig_1.updateAwsCredentials)(credentials);
|
|
108
|
+
credentials = yield (0, sso_1.getOIDCCredentialsFromAccessToken)(credentials, instance.searchMetadata.AccountId);
|
|
109
|
+
}
|
|
110
|
+
credentials.region = config_1.targetRegion;
|
|
93
111
|
debugRole = instance.searchMetadata.AccountId;
|
|
94
112
|
}
|
|
95
113
|
else {
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface IAwsCreds {
|
|
|
5
5
|
region: string;
|
|
6
6
|
accessToken: string;
|
|
7
7
|
ssoAuthn: string;
|
|
8
|
+
refreshToken?: string;
|
|
9
|
+
clientId?: string;
|
|
10
|
+
clientSecret?: string;
|
|
11
|
+
clientSecretExpiresAt?: number;
|
|
12
|
+
accessTokenExpiresAt?: number;
|
|
8
13
|
}
|
|
9
14
|
export interface IAwsCredsRaw {
|
|
10
15
|
region: string;
|
|
@@ -13,6 +18,11 @@ export interface IAwsCredsRaw {
|
|
|
13
18
|
aws_session_token: string;
|
|
14
19
|
aws_access_token: string;
|
|
15
20
|
aws_sso_authn: string;
|
|
21
|
+
aws_sso_refresh_token?: string;
|
|
22
|
+
aws_sso_client_id?: string;
|
|
23
|
+
aws_sso_client_secret?: string;
|
|
24
|
+
aws_sso_client_secret_expires_at?: string;
|
|
25
|
+
aws_sso_access_token_expires_at?: string;
|
|
16
26
|
aws_application_id: string;
|
|
17
27
|
}
|
|
18
28
|
export interface IAppInstances {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"author": "andreigec@hotmail.com",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"private": false,
|
|
8
|
-
"version": "0.0.
|
|
8
|
+
"version": "0.0.292",
|
|
9
9
|
"preferGlobal": true,
|
|
10
10
|
"bin": {
|
|
11
11
|
"ag-awsauth": "./bin/awsauth.js"
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"scripts": {
|
|
50
50
|
"lint": "eslint src",
|
|
51
51
|
"format": "eslint src --fix",
|
|
52
|
+
"test": "tsc && node --test test/*.test.js",
|
|
52
53
|
"start": "tsc && node bin/awsauth.js",
|
|
53
54
|
"build": "tsc"
|
|
54
55
|
}
|