@distilled.cloud/aws 0.2.0-alpha → 0.2.3
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 +61 -0
- package/lib/auth.d.ts +79 -0
- package/lib/auth.d.ts.map +1 -0
- package/lib/auth.js +148 -0
- package/lib/auth.js.map +1 -0
- package/lib/client/api.js +1 -1
- package/lib/client/api.js.map +1 -1
- package/lib/credentials.d.ts +52 -69
- package/lib/credentials.d.ts.map +1 -1
- package/lib/credentials.js +76 -162
- package/lib/credentials.js.map +1 -1
- package/lib/eventstream/codec.d.ts +3 -3
- package/lib/index.d.ts +6 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -0
- package/lib/index.js.map +1 -1
- package/lib/traits.d.ts +1 -1
- package/lib/traits.d.ts.map +1 -1
- package/package.json +12 -7
- package/src/auth.ts +315 -0
- package/src/client/api.ts +1 -1
- package/src/credentials.ts +148 -300
- package/src/index.ts +7 -0
package/src/credentials.ts
CHANGED
|
@@ -7,23 +7,19 @@ import {
|
|
|
7
7
|
fromProcess as _fromProcess,
|
|
8
8
|
fromTokenFile as _fromTokenFile,
|
|
9
9
|
} from "@aws-sdk/credential-providers";
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import {
|
|
12
12
|
type AwsCredentialIdentity,
|
|
13
13
|
type AwsCredentialIdentityProvider,
|
|
14
14
|
} from "@smithy/types";
|
|
15
|
-
import * as Console from "effect/Console";
|
|
16
15
|
import * as Data from "effect/Data";
|
|
17
16
|
import * as Effect from "effect/Effect";
|
|
18
|
-
import * as FileSystem from "effect/FileSystem";
|
|
19
17
|
import * as Layer from "effect/Layer";
|
|
20
|
-
import
|
|
18
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
21
19
|
import * as Redacted from "effect/Redacted";
|
|
22
20
|
import * as ServiceMap from "effect/ServiceMap";
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
25
|
-
import * as path from "node:path";
|
|
26
|
-
import { parseIni, parseSSOSessionData } from "./util/parse-ini.ts";
|
|
21
|
+
import type { HttpClientError } from "effect/unstable/http/HttpClientError";
|
|
22
|
+
import { Auth } from "./auth.ts";
|
|
27
23
|
export * as AWSTypes from "@aws-sdk/types";
|
|
28
24
|
|
|
29
25
|
export interface AwsCredentials {
|
|
@@ -32,31 +28,61 @@ export interface AwsCredentials {
|
|
|
32
28
|
readonly sessionToken?: string;
|
|
33
29
|
}
|
|
34
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Resolved credential values ready for request signing.
|
|
33
|
+
*/
|
|
34
|
+
export interface ResolvedCredentials {
|
|
35
|
+
readonly accessKeyId: Redacted.Redacted<string>;
|
|
36
|
+
readonly secretAccessKey: Redacted.Redacted<string>;
|
|
37
|
+
readonly sessionToken: Redacted.Redacted<string> | undefined;
|
|
38
|
+
readonly expiration?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The requirements for resolving credentials (HttpClient for SSO, FileSystem for cache).
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Error types that can occur during credential resolution.
|
|
47
|
+
*/
|
|
48
|
+
export type CredentialsError =
|
|
49
|
+
| AwsCredentialProviderError
|
|
50
|
+
| ProfileNotFound
|
|
51
|
+
| InvalidSSOProfile
|
|
52
|
+
| InvalidSSOToken
|
|
53
|
+
| ExpiredSSOToken
|
|
54
|
+
| ConflictingSSORegion
|
|
55
|
+
| ConflictingSSOStartUrl
|
|
56
|
+
| HttpClientError
|
|
57
|
+
| PlatformError;
|
|
58
|
+
|
|
35
59
|
export class Credentials extends ServiceMap.Service<
|
|
36
60
|
Credentials,
|
|
37
|
-
|
|
38
|
-
accessKeyId: Redacted.Redacted<string>;
|
|
39
|
-
secretAccessKey: Redacted.Redacted<string>;
|
|
40
|
-
sessionToken: Redacted.Redacted<string> | undefined;
|
|
41
|
-
expiration?: number;
|
|
42
|
-
}
|
|
61
|
+
Effect.Effect<ResolvedCredentials, CredentialsError>
|
|
43
62
|
>()("AWS::Credentials") {}
|
|
44
63
|
|
|
45
|
-
export const mock = Layer.succeed(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
export const mock = Layer.succeed(
|
|
65
|
+
Credentials,
|
|
66
|
+
Effect.succeed({
|
|
67
|
+
accessKeyId: Redacted.make("test"),
|
|
68
|
+
secretAccessKey: Redacted.make("test"),
|
|
69
|
+
sessionToken: Redacted.make("test"),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
50
72
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Create resolved credentials from an AWS credential identity.
|
|
75
|
+
*/
|
|
76
|
+
export const fromAwsCredentialIdentity = (
|
|
77
|
+
identity: AwsCredentialIdentity,
|
|
78
|
+
): ResolvedCredentials => ({
|
|
79
|
+
accessKeyId: Redacted.make(identity.accessKeyId),
|
|
80
|
+
secretAccessKey: Redacted.make(identity.secretAccessKey),
|
|
81
|
+
sessionToken: identity.sessionToken
|
|
82
|
+
? Redacted.make(identity.sessionToken)
|
|
83
|
+
: undefined,
|
|
84
|
+
expiration: identity.expiration?.getTime(),
|
|
85
|
+
});
|
|
60
86
|
|
|
61
87
|
type ProviderName =
|
|
62
88
|
| "env"
|
|
@@ -101,59 +127,96 @@ const providerHints = (
|
|
|
101
127
|
|
|
102
128
|
export const _providerHints = providerHints;
|
|
103
129
|
|
|
104
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Time window (5 mins) to refresh credentials before they actually expire.
|
|
132
|
+
* This prevents using credentials that are about to expire.
|
|
133
|
+
*/
|
|
134
|
+
const CREDENTIAL_REFRESH_WINDOW_MS = 5 * 60 * 1000;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create a credentials effect with lazy resolution and expiration-aware caching.
|
|
138
|
+
* Uses Effect.cachedWithTTL where the TTL is computed from the credentials' expiration.
|
|
139
|
+
*/
|
|
140
|
+
const createCachedCredentialsEffect = <E, R>(
|
|
141
|
+
resolve: Effect.Effect<ResolvedCredentials, E, R>,
|
|
142
|
+
): Effect.Effect<ResolvedCredentials, E, R> => {
|
|
143
|
+
let cachedCreds: ResolvedCredentials | undefined;
|
|
144
|
+
let expiresAt: number | undefined;
|
|
145
|
+
|
|
146
|
+
return Effect.suspend(() => {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
if (cachedCreds && expiresAt && now < expiresAt) {
|
|
149
|
+
return Effect.succeed(cachedCreds);
|
|
150
|
+
}
|
|
151
|
+
return Effect.map(resolve, (creds) => {
|
|
152
|
+
cachedCreds = creds;
|
|
153
|
+
expiresAt = creds.expiration
|
|
154
|
+
? creds.expiration - CREDENTIAL_REFRESH_WINDOW_MS
|
|
155
|
+
: undefined;
|
|
156
|
+
return creds;
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a lazy, cached credentials provider from an AWS SDK credential provider.
|
|
163
|
+
* Credentials are resolved on first access and cached based on their expiration time.
|
|
164
|
+
*/
|
|
165
|
+
const createLazyProvider = (
|
|
105
166
|
provider: (config: {}) => AwsCredentialIdentityProvider,
|
|
106
167
|
providerName: ProviderName,
|
|
107
|
-
) =>
|
|
108
|
-
|
|
168
|
+
): Layer.Layer<Credentials> => {
|
|
169
|
+
const resolve = Effect.gen(function* () {
|
|
170
|
+
const hints = providerHints(providerName);
|
|
171
|
+
const identity = yield* Effect.tryPromise({
|
|
172
|
+
try: () => provider({})(),
|
|
173
|
+
catch: (cause) =>
|
|
174
|
+
new AwsCredentialProviderError({
|
|
175
|
+
message: `Failed to resolve credentials from ${providerName}.`,
|
|
176
|
+
provider: providerName,
|
|
177
|
+
cause,
|
|
178
|
+
hints,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
return fromAwsCredentialIdentity(identity);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return Layer.succeed(Credentials, createCachedCredentialsEffect(resolve));
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create a credentials provider from static credentials.
|
|
189
|
+
* No lazy loading or caching needed since credentials are already available.
|
|
190
|
+
*/
|
|
191
|
+
export const fromCredentials = (
|
|
192
|
+
credentials: AwsCredentialIdentity,
|
|
193
|
+
): Layer.Layer<Credentials> =>
|
|
194
|
+
Layer.succeed(
|
|
109
195
|
Credentials,
|
|
110
|
-
Effect.
|
|
111
|
-
const hints = providerHints(providerName);
|
|
112
|
-
const identity = yield* Effect.tryPromise({
|
|
113
|
-
try: () => provider({})(),
|
|
114
|
-
catch: (cause) =>
|
|
115
|
-
new AwsCredentialProviderError({
|
|
116
|
-
message: `Failed to resolve credentials from ${providerName}.`,
|
|
117
|
-
provider: providerName,
|
|
118
|
-
cause,
|
|
119
|
-
hints,
|
|
120
|
-
}),
|
|
121
|
-
});
|
|
122
|
-
return fromAwsCredentialIdentity(identity);
|
|
123
|
-
}),
|
|
196
|
+
Effect.succeed(fromAwsCredentialIdentity(credentials)),
|
|
124
197
|
);
|
|
125
198
|
|
|
126
|
-
export const
|
|
127
|
-
Layer.succeed(Credentials, fromAwsCredentialIdentity(credentials));
|
|
128
|
-
|
|
129
|
-
export const fromEnv = () => createLayer(_fromEnv, "env");
|
|
199
|
+
export const fromEnv = () => createLazyProvider(_fromEnv, "env");
|
|
130
200
|
|
|
131
201
|
export const fromChain = () =>
|
|
132
|
-
|
|
202
|
+
createLazyProvider(() => _fromNodeProviderChain(), "chain");
|
|
133
203
|
|
|
134
|
-
// export const fromSSO = () =>
|
|
204
|
+
// export const fromSSO = () => createLazyProvider(_fromSSO);
|
|
135
205
|
|
|
136
|
-
export const fromIni = () =>
|
|
206
|
+
export const fromIni = () => createLazyProvider(_fromIni, "ini");
|
|
137
207
|
|
|
138
208
|
export const fromContainerMetadata = () =>
|
|
139
|
-
|
|
209
|
+
createLazyProvider(_fromContainerMetadata, "container");
|
|
140
210
|
|
|
141
|
-
export const fromHttp = () =>
|
|
211
|
+
export const fromHttp = () => createLazyProvider(_fromHttp, "http");
|
|
142
212
|
|
|
143
|
-
export const fromProcess = () =>
|
|
213
|
+
export const fromProcess = () => createLazyProvider(_fromProcess, "process");
|
|
144
214
|
|
|
145
|
-
export const fromTokenFile = () =>
|
|
215
|
+
export const fromTokenFile = () =>
|
|
216
|
+
createLazyProvider(_fromTokenFile, "token-file");
|
|
146
217
|
|
|
147
218
|
export const ssoRegion = (region: string) => Layer.succeed(SsoRegion, region);
|
|
148
219
|
|
|
149
|
-
/**
|
|
150
|
-
* The time window (5 mins) that SDK will treat the SSO token expires in before the defined expiration date in token.
|
|
151
|
-
* This is needed because server side may have invalidated the token before the defined expiration date.
|
|
152
|
-
*/
|
|
153
|
-
const EXPIRE_WINDOW_MS = 5 * 60 * 1000;
|
|
154
|
-
|
|
155
|
-
const REFRESH_MESSAGE = `To refresh this SSO session run 'aws sso login' with the corresponding profile.`;
|
|
156
|
-
|
|
157
220
|
export class SsoRegion extends ServiceMap.Service<SsoRegion, string>()(
|
|
158
221
|
"AWS::SsoRegion",
|
|
159
222
|
) {}
|
|
@@ -161,13 +224,15 @@ export class SsoStartUrl extends ServiceMap.Service<SsoStartUrl, string>()(
|
|
|
161
224
|
"AWS::SsoStartUrl",
|
|
162
225
|
) {}
|
|
163
226
|
|
|
164
|
-
export class ProfileNotFound extends Data.TaggedError(
|
|
227
|
+
export class ProfileNotFound extends Data.TaggedError(
|
|
228
|
+
"Alchemy::AWS::ProfileNotFound",
|
|
229
|
+
)<{
|
|
165
230
|
message: string;
|
|
166
231
|
profile: string;
|
|
167
232
|
}> {}
|
|
168
233
|
|
|
169
234
|
export class ConflictingSSORegion extends Data.TaggedError(
|
|
170
|
-
"AWS::ConflictingSSORegion",
|
|
235
|
+
"Alchemy::AWS::ConflictingSSORegion",
|
|
171
236
|
)<{
|
|
172
237
|
message: string;
|
|
173
238
|
ssoRegion: string;
|
|
@@ -175,7 +240,7 @@ export class ConflictingSSORegion extends Data.TaggedError(
|
|
|
175
240
|
}> {}
|
|
176
241
|
|
|
177
242
|
export class ConflictingSSOStartUrl extends Data.TaggedError(
|
|
178
|
-
"AWS::ConflictingSSOStartUrl",
|
|
243
|
+
"Alchemy::AWS::ConflictingSSOStartUrl",
|
|
179
244
|
)<{
|
|
180
245
|
message: string;
|
|
181
246
|
ssoStartUrl: string;
|
|
@@ -183,19 +248,23 @@ export class ConflictingSSOStartUrl extends Data.TaggedError(
|
|
|
183
248
|
}> {}
|
|
184
249
|
|
|
185
250
|
export class InvalidSSOProfile extends Data.TaggedError(
|
|
186
|
-
"AWS::InvalidSSOProfile",
|
|
251
|
+
"Alchemy::AWS::InvalidSSOProfile",
|
|
187
252
|
)<{
|
|
188
253
|
message: string;
|
|
189
254
|
profile: string;
|
|
190
255
|
missingFields: string[];
|
|
191
256
|
}> {}
|
|
192
257
|
|
|
193
|
-
export class InvalidSSOToken extends Data.TaggedError(
|
|
258
|
+
export class InvalidSSOToken extends Data.TaggedError(
|
|
259
|
+
"Alchemy::AWS::InvalidSSOToken",
|
|
260
|
+
)<{
|
|
194
261
|
message: string;
|
|
195
262
|
sso_session: string;
|
|
196
263
|
}> {}
|
|
197
264
|
|
|
198
|
-
export class ExpiredSSOToken extends Data.TaggedError(
|
|
265
|
+
export class ExpiredSSOToken extends Data.TaggedError(
|
|
266
|
+
"Alchemy::AWS::ExpiredSSOToken",
|
|
267
|
+
)<{
|
|
199
268
|
message: string;
|
|
200
269
|
profile: string;
|
|
201
270
|
}> {}
|
|
@@ -209,238 +278,17 @@ export class AwsCredentialProviderError extends Data.TaggedError(
|
|
|
209
278
|
hints?: ReadonlyArray<string>;
|
|
210
279
|
}> {}
|
|
211
280
|
|
|
212
|
-
export interface AwsProfileConfig {
|
|
213
|
-
sso_session?: string;
|
|
214
|
-
sso_account_id?: string;
|
|
215
|
-
sso_role_name?: string;
|
|
216
|
-
region?: string;
|
|
217
|
-
output?: string;
|
|
218
|
-
sso_start_url?: string;
|
|
219
|
-
sso_region?: string;
|
|
220
|
-
}
|
|
221
|
-
export interface SsoProfileConfig extends AwsProfileConfig {
|
|
222
|
-
sso_start_url: string;
|
|
223
|
-
sso_region: string;
|
|
224
|
-
sso_account_id: string;
|
|
225
|
-
sso_role_name: string;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
281
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
282
|
+
* Create a lazy, cached SSO credentials provider.
|
|
283
|
+
* SSO credential resolution is deferred until the Effect is run,
|
|
284
|
+
* and credentials are cached until they expire.
|
|
231
285
|
*/
|
|
232
|
-
export interface SSOToken {
|
|
233
|
-
accessToken: string;
|
|
234
|
-
expiresAt: string;
|
|
235
|
-
refreshToken?: string;
|
|
236
|
-
clientId?: string;
|
|
237
|
-
clientSecret?: string;
|
|
238
|
-
registrationExpiresAt?: string;
|
|
239
|
-
region?: string;
|
|
240
|
-
startUrl?: string;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
286
|
export const fromSSO = (profileName: string = "default") =>
|
|
244
|
-
Layer.effect(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const awsDir = path.join(ini.getHomeDir(), ".aws");
|
|
250
|
-
const cachePath = path.join(awsDir, "sso", "cache");
|
|
251
|
-
|
|
252
|
-
const profile = yield* loadProfile(profileName);
|
|
253
|
-
|
|
254
|
-
if (profile.sso_session) {
|
|
255
|
-
const hasher = createHash("sha1");
|
|
256
|
-
const cacheName = hasher.update(profile.sso_session).digest("hex");
|
|
257
|
-
const ssoTokenFilepath = path.join(cachePath, `${cacheName}.json`);
|
|
258
|
-
const cachedCredsFilePath = path.join(
|
|
259
|
-
cachePath,
|
|
260
|
-
`${cacheName}.credentials.json`,
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
const cachedCreds = yield* fs.readFileString(cachedCredsFilePath).pipe(
|
|
264
|
-
Effect.map((text) => JSON.parse(text)),
|
|
265
|
-
Effect.catch(() => Effect.void),
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
const isExpired = (expiry: number | string | undefined) => {
|
|
269
|
-
return (
|
|
270
|
-
expiry === undefined ||
|
|
271
|
-
new Date(expiry).getTime() - Date.now() <= EXPIRE_WINDOW_MS
|
|
272
|
-
);
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
if (cachedCreds && !isExpired(cachedCreds.expiry)) {
|
|
276
|
-
return Credentials.of({
|
|
277
|
-
accessKeyId: Redacted.make(cachedCreds.accessKeyId),
|
|
278
|
-
secretAccessKey: Redacted.make(cachedCreds.secretAccessKey),
|
|
279
|
-
sessionToken: cachedCreds.sessionToken
|
|
280
|
-
? Redacted.make(cachedCreds.sessionToken)
|
|
281
|
-
: undefined,
|
|
282
|
-
expiration: cachedCreds.expiry,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const ssoToken = yield* fs.readFileString(ssoTokenFilepath).pipe(
|
|
287
|
-
Effect.map((text) => JSON.parse(text) as SSOToken),
|
|
288
|
-
Effect.catch(() =>
|
|
289
|
-
Effect.fail(
|
|
290
|
-
new InvalidSSOToken({
|
|
291
|
-
message: `The SSO session token associated with profile=${profileName} was not found or is invalid. ${REFRESH_MESSAGE}`,
|
|
292
|
-
sso_session: profile.sso_session!,
|
|
293
|
-
}),
|
|
294
|
-
),
|
|
287
|
+
Layer.effect(
|
|
288
|
+
Credentials,
|
|
289
|
+
Auth.use((auth) =>
|
|
290
|
+
Effect.succeed(
|
|
291
|
+
createCachedCredentialsEffect(auth.loadProfileCredentials(profileName)),
|
|
295
292
|
),
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
if (isExpired(ssoToken.expiresAt)) {
|
|
299
|
-
yield* Console.log(
|
|
300
|
-
`The SSO session token associated with profile=${profileName} was not found or is invalid. ${REFRESH_MESSAGE}`,
|
|
301
|
-
);
|
|
302
|
-
yield* Effect.fail(
|
|
303
|
-
new ExpiredSSOToken({
|
|
304
|
-
message: `The SSO session token associated with profile=${profileName} was not found or is invalid. ${REFRESH_MESSAGE}`,
|
|
305
|
-
profile: profileName,
|
|
306
|
-
}),
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const response = yield* client.get(
|
|
311
|
-
`https://portal.sso.${profile.sso_region}.amazonaws.com/federation/credentials?account_id=${profile.sso_account_id}&role_name=${profile.sso_role_name}`,
|
|
312
|
-
{
|
|
313
|
-
headers: {
|
|
314
|
-
"User-Agent": "alchemy.run",
|
|
315
|
-
"Content-Type": "application/json",
|
|
316
|
-
"x-amz-sso_bearer_token": ssoToken.accessToken,
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const credentials = (
|
|
322
|
-
(yield* response.json) as {
|
|
323
|
-
roleCredentials: {
|
|
324
|
-
accessKeyId: string;
|
|
325
|
-
secretAccessKey: string;
|
|
326
|
-
sessionToken: string;
|
|
327
|
-
expiration: number;
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
).roleCredentials;
|
|
331
|
-
|
|
332
|
-
yield* fs.writeFileString(
|
|
333
|
-
cachedCredsFilePath,
|
|
334
|
-
JSON.stringify({
|
|
335
|
-
accessKeyId: credentials.accessKeyId,
|
|
336
|
-
secretAccessKey: credentials.secretAccessKey,
|
|
337
|
-
sessionToken: credentials.sessionToken,
|
|
338
|
-
expiry: credentials.expiration,
|
|
339
|
-
}),
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
return Credentials.of({
|
|
343
|
-
accessKeyId: Redacted.make(credentials.accessKeyId),
|
|
344
|
-
secretAccessKey: Redacted.make(credentials.secretAccessKey),
|
|
345
|
-
sessionToken: Redacted.make(credentials.sessionToken),
|
|
346
|
-
expiration: credentials.expiration,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return yield* Effect.fail(
|
|
351
|
-
new ProfileNotFound({
|
|
352
|
-
message: `Profile ${profileName} not found`,
|
|
353
|
-
profile: profileName,
|
|
354
|
-
}),
|
|
355
|
-
);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
export const loadProfile = Effect.fn(function* (profileName: string) {
|
|
359
|
-
const fs = yield* FileSystem.FileSystem;
|
|
360
|
-
const profiles: {
|
|
361
|
-
[profileName: string]: AwsProfileConfig;
|
|
362
|
-
} = yield* Effect.promise(() =>
|
|
363
|
-
ini.parseKnownFiles({ profile: profileName }),
|
|
293
|
+
),
|
|
364
294
|
);
|
|
365
|
-
|
|
366
|
-
const profile = profiles[profileName];
|
|
367
|
-
|
|
368
|
-
if (!profile) {
|
|
369
|
-
yield* Effect.fail(
|
|
370
|
-
new ProfileNotFound({
|
|
371
|
-
message: `Profile ${profileName} not found`,
|
|
372
|
-
profile: profileName,
|
|
373
|
-
}),
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const awsDir = path.join(ini.getHomeDir(), ".aws");
|
|
378
|
-
const configPath = path.join(awsDir, "config");
|
|
379
|
-
|
|
380
|
-
if (profile.sso_session) {
|
|
381
|
-
const ssoRegion = Option.getOrUndefined(
|
|
382
|
-
yield* Effect.serviceOption(SsoRegion),
|
|
383
|
-
);
|
|
384
|
-
const ssoStartUrl = Option.getOrElse(
|
|
385
|
-
yield* Effect.serviceOption(SsoStartUrl),
|
|
386
|
-
() => profile.sso_start_url,
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
const ssoSessions = yield* fs.readFileString(configPath).pipe(
|
|
390
|
-
Effect.flatMap((config) => Effect.promise(async () => parseIni(config))),
|
|
391
|
-
Effect.map(parseSSOSessionData),
|
|
392
|
-
);
|
|
393
|
-
const session = ssoSessions[profile.sso_session];
|
|
394
|
-
if (ssoRegion && ssoRegion !== session.sso_region) {
|
|
395
|
-
yield* Effect.fail(
|
|
396
|
-
new ConflictingSSORegion({
|
|
397
|
-
message: `Conflicting SSO region`,
|
|
398
|
-
ssoRegion: ssoRegion,
|
|
399
|
-
profile: profile.sso_session,
|
|
400
|
-
}),
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
if (ssoStartUrl && ssoStartUrl !== session.sso_start_url) {
|
|
404
|
-
yield* Effect.fail(
|
|
405
|
-
new ConflictingSSOStartUrl({
|
|
406
|
-
message: `Conflicting SSO start url`,
|
|
407
|
-
ssoStartUrl: ssoStartUrl,
|
|
408
|
-
profile: profile.sso_session,
|
|
409
|
-
}),
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
profile.sso_region = session.sso_region;
|
|
413
|
-
profile.sso_start_url = session.sso_start_url;
|
|
414
|
-
|
|
415
|
-
const ssoFields = [
|
|
416
|
-
"sso_start_url",
|
|
417
|
-
"sso_account_id",
|
|
418
|
-
"sso_region",
|
|
419
|
-
"sso_role_name",
|
|
420
|
-
] as const satisfies (keyof SsoProfileConfig)[];
|
|
421
|
-
const missingFields = ssoFields.filter((field) => !profile[field]);
|
|
422
|
-
if (missingFields.length > 0) {
|
|
423
|
-
yield* Effect.fail(
|
|
424
|
-
new InvalidSSOProfile({
|
|
425
|
-
profile: profileName,
|
|
426
|
-
missingFields,
|
|
427
|
-
message:
|
|
428
|
-
`Profile is configured with invalid SSO credentials. Required parameters "sso_account_id", ` +
|
|
429
|
-
`"sso_region", "sso_role_name", "sso_start_url". Got ${Object.keys(
|
|
430
|
-
profile,
|
|
431
|
-
).join(
|
|
432
|
-
", ",
|
|
433
|
-
)}\nReference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html`,
|
|
434
|
-
}),
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
return profile;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return yield* Effect.fail(
|
|
441
|
-
new ProfileNotFound({
|
|
442
|
-
message: `Profile ${profileName} not found`,
|
|
443
|
-
profile: profileName,
|
|
444
|
-
}),
|
|
445
|
-
);
|
|
446
|
-
});
|
package/src/index.ts
CHANGED