Skip to content

Commit 6b8ac98

Browse files
auth refactoring (#3375)
Doing some refactoring of the auth related code. Includes naming changes, extracting logic in to its own function, centralizing code. See commit messages for specific info on each change. Signed-off-by: Nikolas Komonen <[email protected]>
1 parent d625ddf commit 6b8ac98

File tree

12 files changed

+170
-150
lines changed

12 files changed

+170
-150
lines changed

src/credentials/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import { telemetry } from '../shared/telemetry/telemetry'
3636
import { createCommonButtons, createExitButton, createHelpButton, createRefreshButton } from '../shared/ui/buttons'
3737
import { getIdeProperties, isCloud9 } from '../shared/extensionUtilities'
3838
import { getCodeCatalystDevEnvId } from '../shared/vscode/env'
39-
import { getConfigFilename } from './sharedCredentials'
4039
import { authHelpUrl } from '../shared/constants'
4140
import { getDependentAuths } from './secondaryAuth'
4241
import { DevSettings } from '../shared/settings'
@@ -45,6 +44,7 @@ import { createRegionPrompter } from '../shared/ui/common/region'
4544
import { SsoCredentialsProvider } from './providers/ssoCredentialsProvider'
4645
import { AsyncCollection, toCollection } from '../shared/utilities/asyncCollection'
4746
import { join, toStream } from '../shared/utilities/collectionUtils'
47+
import { getConfigFilename } from './sharedCredentialsFile'
4848

4949
export const ssoScope = 'sso:account:access'
5050
export const codecatalystScopes = ['codecatalyst:read_write']

src/credentials/providers/sharedCredentialsProvider.ts

Lines changed: 60 additions & 81 deletions
Large diffs are not rendered by default.

src/credentials/providers/sharedCredentialsProviderFactory.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55

66
import * as fs from 'fs-extra'
77
import { getLogger, Logger } from '../../shared/logger'
8-
import {
9-
getConfigFilename,
10-
getCredentialsFilename,
11-
loadSharedCredentialsSections,
12-
updateAwsSdkLoadConfigEnvVar,
13-
} from '../sharedCredentials'
8+
import { loadSharedCredentialsSections, updateAwsSdkLoadConfigEnvVar } from '../sharedCredentials'
149
import { CredentialsProviderType } from './credentials'
1510
import { BaseCredentialsProviderFactory } from './credentialsProviderFactory'
1611
import { SharedCredentialsProvider } from './sharedCredentialsProvider'
12+
import { getCredentialsFilename, getConfigFilename } from '../sharedCredentialsFile'
1713

1814
export class SharedCredentialsProviderFactory extends BaseCredentialsProviderFactory<SharedCredentialsProvider> {
1915
private readonly logger: Logger = getLogger()

src/credentials/sharedCredentials.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,10 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { join } from 'path'
8-
import { EnvironmentVariables } from '../shared/environmentVariables'
97
import { SystemUtilities } from '../shared/systemUtilities'
108
import { ToolkitError } from '../shared/errors'
119
import { assertHasProps } from '../shared/utilities/tsUtils'
12-
13-
export function getCredentialsFilename(): string {
14-
const env = process.env as EnvironmentVariables
15-
16-
return env.AWS_SHARED_CREDENTIALS_FILE || join(SystemUtilities.getHomeDirectory(), '.aws', 'credentials')
17-
}
18-
19-
export function getConfigFilename(): string {
20-
const env = process.env as EnvironmentVariables
21-
22-
return env.AWS_CONFIG_FILE || join(SystemUtilities.getHomeDirectory(), '.aws', 'config')
23-
}
10+
import { getConfigFilename, getCredentialsFilename } from './sharedCredentialsFile'
2411

2512
export async function updateAwsSdkLoadConfigEnvVar(): Promise<void> {
2613
const configFileExists = await SystemUtilities.fileExists(getConfigFilename())
@@ -35,7 +22,7 @@ interface AssignmentNode {
3522

3623
export interface BaseSection {
3724
readonly type: string
38-
readonly name: string
25+
readonly name: SectionName
3926
readonly source: vscode.Uri
4027
readonly startLines: number[]
4128
readonly assignments: AssignmentNode[]
@@ -75,7 +62,7 @@ export class ParseError extends Error {
7562
export const isProfileSection = (section: Section): section is ProfileSection => section.type === 'profile'
7663
export const isSsoSessionSection = (section: Section): section is SsoSessionSection => section.type === 'sso-session'
7764

78-
export function extractData(section: BaseSection): Record<string, string> {
65+
export function extractDataFromSection(section: BaseSection): Record<string, string> {
7966
const data: Record<string, string> = {}
8067
for (const assignment of section.assignments) {
8168
data[assignment.key] = assignment.value
@@ -86,7 +73,7 @@ export function extractData(section: BaseSection): Record<string, string> {
8673

8774
export function getRequiredFields<T extends string>(section: Section, ...keys: T[]): { [P in T]: string } {
8875
try {
89-
const data = extractData(section)
76+
const data = extractDataFromSection(section)
9077
assertHasProps(data, ...keys)
9178

9279
return data
@@ -99,7 +86,7 @@ export function getRequiredFields<T extends string>(section: Section, ...keys: T
9986

10087
export function getSectionOrThrow<T extends Section['type'] = Section['type']>(
10188
sections: Section[],
102-
name: string,
89+
name: SectionName,
10390
type: T
10491
): Section & { type: T } {
10592
const section = sections.find(s => s.name === name && s.type === type) as (Section & { type: T }) | undefined
@@ -111,13 +98,13 @@ export function getSectionOrThrow<T extends Section['type'] = Section['type']>(
11198
return section
11299
}
113100

114-
export function getSectionDataOrThrow(sections: Section[], name: string, type: Section['type']) {
101+
export function getSectionDataOrThrow(sections: Section[], name: SectionName, type: Section['type']) {
115102
const section = getSectionOrThrow(sections, name, type)
116103
if (section.type !== type) {
117104
throw ParseError.fromSection(section, `Expected section to be type "${type}", got: ${section.type}`)
118105
}
119106

120-
return extractData(section)
107+
return extractDataFromSection(section)
121108
}
122109

123110
const sectionTypes = ['profile', 'sso-session'] as const
@@ -130,6 +117,19 @@ function validateSection(section: BaseSection): asserts section is Section {
130117
}
131118
}
132119

120+
/**
121+
* Loads existing merged (credentials + config) profiles from the filesystem
122+
*/
123+
export async function loadSharedCredentialsProfiles(): Promise<Record<SectionName, Profile>> {
124+
const profiles = {} as Record<SectionName, Profile>
125+
for (const [k, v] of (await loadSharedCredentialsSections()).sections.entries()) {
126+
if (v.type === 'profile') {
127+
profiles[k] = extractDataFromSection(v)
128+
}
129+
}
130+
return profiles
131+
}
132+
133133
export async function loadSharedCredentialsSections(): Promise<ParseResult> {
134134
// These should eventually be changed to use `parse` to allow for credentials from other file systems
135135
const data = await loadSharedConfigFiles({
@@ -234,6 +234,13 @@ async function loadCredentialsFile(credentialsUri?: vscode.Uri): Promise<ReturnT
234234
return parseIni(await SystemUtilities.readFile(credentialsUri), credentialsUri)
235235
}
236236

237+
/**
238+
* The name of a section in a credentials/config file
239+
*
240+
* The is the value of `{A}` in `[ {A} ]` or `[ {B} {A} ]`.
241+
*/
242+
export type SectionName = string
243+
237244
export interface Profile {
238245
[key: string]: string | undefined
239246
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*!
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* This module focuses on the i/o of the credentials/config files
6+
*/
7+
8+
import { join } from 'path'
9+
import { EnvironmentVariables } from '../shared/environmentVariables'
10+
import { SystemUtilities } from '../shared/systemUtilities'
11+
12+
export function getCredentialsFilename(): string {
13+
const env = process.env as EnvironmentVariables
14+
15+
return env.AWS_SHARED_CREDENTIALS_FILE || join(SystemUtilities.getHomeDirectory(), '.aws', 'credentials')
16+
}
17+
18+
export function getConfigFilename(): string {
19+
const env = process.env as EnvironmentVariables
20+
21+
return env.AWS_CONFIG_FILE || join(SystemUtilities.getHomeDirectory(), '.aws', 'config')
22+
}

src/credentials/types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*!
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* All keys that are shared between a credentials or config file
8+
*/
9+
export const SharedCredentialsKeys = {
10+
AWS_ACCESS_KEY_ID: 'aws_access_key_id',
11+
AWS_SECRET_ACCESS_KEY: 'aws_secret_access_key',
12+
AWS_SESSION_TOKEN: 'aws_session_token',
13+
CREDENTIAL_PROCESS: 'credential_process',
14+
CREDENTIAL_SOURCE: 'credential_source',
15+
REGION: 'region',
16+
ROLE_ARN: 'role_arn',
17+
SOURCE_PROFILE: 'source_profile',
18+
MFA_SERIAL: 'mfa_serial',
19+
SSO_START_URL: 'sso_start_url',
20+
SSO_REGION: 'sso_region',
21+
SSO_ACCOUNT_ID: 'sso_account_id',
22+
SSO_ROLE_NAME: 'sso_role_name',
23+
SSO_SESSION: 'sso_session',
24+
SSO_REGISTRATION_SCOPES: 'sso_registration_scopes',
25+
} as const
26+
27+
/**
28+
* The required keys for a static credentials profile
29+
*
30+
* https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html
31+
*/
32+
export interface StaticCredentialsProfileData {
33+
[SharedCredentialsKeys.AWS_ACCESS_KEY_ID]: string
34+
[SharedCredentialsKeys.AWS_SECRET_ACCESS_KEY]: string
35+
}

src/credentials/wizards/createProfile.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { StepEstimator, Wizard, WIZARD_BACK } from '../../shared/wizards/wizard'
1111
import { Prompter, PromptResult } from '../../shared/ui/prompter'
1212
import { createInputBox } from '../../shared/ui/inputPrompter'
1313
import { DefaultStsClient } from '../../shared/clients/stsClient'
14-
import { ProfileKey } from './templates'
1514
import { createCommonButtons } from '../../shared/ui/buttons'
1615
import { credentialHelpUrl } from '../../shared/constants'
1716
import { showLoginFailedMessage } from '../credentialsUtilities'
1817
import { ParsedIniData, Profile } from '../sharedCredentials'
18+
import { SharedCredentialsKeys } from '../types'
1919

2020
function createProfileNamePrompter(profiles: ParsedIniData) {
2121
return createInputBox({
@@ -82,8 +82,8 @@ class ProfileChecker<T extends Profile> extends Prompter<string> {
8282
try {
8383
const region = this.profile['region'] ?? 'us-east-1' // De-dupe this pattern
8484
const stsClient = new DefaultStsClient(region, {
85-
accessKeyId: this.profile[ProfileKey.AccessKeyId]!,
86-
secretAccessKey: this.profile[ProfileKey.SecretKey]!,
85+
accessKeyId: this.profile[SharedCredentialsKeys.AWS_ACCESS_KEY_ID]!,
86+
secretAccessKey: this.profile[SharedCredentialsKeys.AWS_SECRET_ACCESS_KEY]!,
8787
})
8888

8989
return (await stsClient.getCallerIdentity()).Account

src/credentials/wizards/templates.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,19 @@ import { getIdeProperties } from '../../shared/extensionUtilities'
1111
import { ProfileTemplateProvider } from './createProfile'
1212
import { createCommonButtons } from '../../shared/ui/buttons'
1313
import { credentialHelpUrl } from '../../shared/constants'
14-
15-
// TODO: use this everywhere else
16-
export enum ProfileKey {
17-
AccessKeyId = 'aws_access_key_id',
18-
SecretKey = 'aws_secret_access_key',
19-
Process = 'credential_process',
20-
}
14+
import { SharedCredentialsKeys, StaticCredentialsProfileData } from '../types'
2115

2216
function getTitle(profileName: string): string {
2317
return localize('AWS.title.createCredentialProfile', 'Creating new profile "{0}"', profileName)
2418
}
2519

26-
interface StaticProfile {
27-
[ProfileKey.AccessKeyId]: string
28-
[ProfileKey.SecretKey]: string
29-
}
30-
3120
const accessKeyPattern = /[\w]{16,128}/
3221

33-
export const staticCredentialsTemplate: ProfileTemplateProvider<StaticProfile> = {
22+
export const staticCredentialsTemplate: ProfileTemplateProvider<StaticCredentialsProfileData> = {
3423
label: 'Static Credentials',
3524
description: 'Use this for credentials that never expire',
3625
prompts: {
37-
[ProfileKey.AccessKeyId]: name =>
26+
[SharedCredentialsKeys.AWS_ACCESS_KEY_ID]: name =>
3827
createInputBox({
3928
title: getTitle(name),
4029
buttons: createCommonButtons(credentialHelpUrl),
@@ -57,7 +46,7 @@ export const staticCredentialsTemplate: ProfileTemplateProvider<StaticProfile> =
5746
}
5847
},
5948
}),
60-
[ProfileKey.SecretKey]: name =>
49+
[SharedCredentialsKeys.AWS_SECRET_ACCESS_KEY]: name =>
6150
createInputBox({
6251
title: getTitle(name),
6352
buttons: createCommonButtons(credentialHelpUrl),
@@ -79,14 +68,14 @@ export const staticCredentialsTemplate: ProfileTemplateProvider<StaticProfile> =
7968
}
8069

8170
interface CredentialsProcessProfile {
82-
[ProfileKey.Process]: string
71+
[SharedCredentialsKeys.CREDENTIAL_PROCESS]: string
8372
}
8473

8574
export const processCredentialsTemplate: ProfileTemplateProvider<CredentialsProcessProfile> = {
8675
label: 'External Process',
8776
description: 'Creates a new profile that fetches credentials from a process',
8877
prompts: {
89-
[ProfileKey.Process]: name =>
78+
[SharedCredentialsKeys.CREDENTIAL_PROCESS]: name =>
9079
createInputBox({
9180
title: getTitle(name),
9281
prompt: 'Enter a command to run',

src/shared/awsContextCommands.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ import { credentialHelpUrl } from './constants'
1717
import { PromptSettings } from './settings'
1818
import { isNonNullable } from './utilities/tsUtils'
1919
import { CreateProfileWizard } from '../credentials/wizards/createProfile'
20-
import { ProfileKey, staticCredentialsTemplate } from '../credentials/wizards/templates'
20+
import { staticCredentialsTemplate } from '../credentials/wizards/templates'
2121
import { SharedCredentialsProvider } from '../credentials/providers/sharedCredentialsProvider'
2222
import { Auth } from '../credentials/auth'
2323
import { CancellationError } from './utilities/timeoutUtils'
2424
import { ToolkitError } from './errors'
25-
import { extractData, loadSharedCredentialsSections, Profile } from '../credentials/sharedCredentials'
25+
import { loadSharedCredentialsProfiles } from '../credentials/sharedCredentials'
26+
import { SharedCredentialsKeys } from '../credentials/types'
2627

2728
/**
2829
* @deprecated
@@ -115,23 +116,17 @@ export class AwsContextCommands {
115116
* @returns The profile name, or undefined if user cancelled
116117
*/
117118
public async promptAndCreateNewCredentialsFile(): Promise<string | undefined> {
118-
const profiles = {} as Record<string, Profile>
119-
for (const [k, v] of (await loadSharedCredentialsSections()).sections.entries()) {
120-
if (v.type === 'profile') {
121-
profiles[k] = extractData(v)
122-
}
123-
}
124-
125-
const wizard = new CreateProfileWizard(profiles, staticCredentialsTemplate)
119+
const existingProfiles = await loadSharedCredentialsProfiles()
120+
const wizard = new CreateProfileWizard(existingProfiles, staticCredentialsTemplate)
126121
const resp = await wizard.run()
127122
if (!resp) {
128123
return
129124
}
130125

131126
await UserCredentialsUtils.generateCredentialsFile({
132127
profileName: resp.name,
133-
accessKey: resp.profile[ProfileKey.AccessKeyId]!,
134-
secretKey: resp.profile[ProfileKey.SecretKey]!,
128+
accessKey: resp.profile[SharedCredentialsKeys.AWS_ACCESS_KEY_ID]!,
129+
secretKey: resp.profile[SharedCredentialsKeys.AWS_SECRET_ACCESS_KEY]!,
135130
})
136131

137132
const sharedProviderId: CredentialsId = {

src/shared/credentials/userCredentialsUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import * as path from 'path'
77
import * as vscode from 'vscode'
88
import { mkdirp, writeFile } from 'fs-extra'
9-
import { getConfigFilename, getCredentialsFilename } from '../../credentials/sharedCredentials'
109
import { fileExists } from '../filesystemUtilities'
1110
import { SystemUtilities } from '../systemUtilities'
1211
import { isNonNullable } from '../utilities/tsUtils'
12+
import { getConfigFilename, getCredentialsFilename } from '../../credentials/sharedCredentialsFile'
1313

1414
const header = `
1515
# AWS credentials file used by AWS CLI, SDKs, and tools.

0 commit comments

Comments
 (0)