Skip to content

Commit 31e3c59

Browse files
committed
Move encryption to LanguageClientAuth and encrypt profile data
1 parent 255214c commit 31e3c59

File tree

1 file changed

+50
-46
lines changed

1 file changed

+50
-46
lines changed

packages/core/src/auth/auth2.ts

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
invalidateSsoTokenRequestType,
2020
invalidateStsCredentialRequestType,
2121
ProfileKind,
22-
UpdateProfileParams,
2322
updateProfileRequestType,
2423
SsoTokenChangedParams,
2524
StsCredentialChangedParams,
@@ -50,8 +49,8 @@ import {
5049
Profile,
5150
SsoSession,
5251
GetMfaCodeParams,
53-
GetMfaCodeResult,
5452
getMfaCodeRequestType,
53+
GetMfaCodeResult,
5554
} from '@aws/language-server-runtimes/protocol'
5655
import { LanguageClient } from 'vscode-languageclient'
5756
import { getLogger } from '../shared/logger/logger'
@@ -125,12 +124,31 @@ export class LanguageClientAuth {
125124
return this.#ssoCacheWatcher
126125
}
127126

128-
getSsoToken(
127+
/**
128+
* Encrypts an object
129+
*/
130+
private async encrypt<T>(request: T): Promise<string> {
131+
const payload = new TextEncoder().encode(JSON.stringify(request))
132+
const encrypted = await new jose.CompactEncrypt(payload)
133+
.setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
134+
.encrypt(this.encryptionKey)
135+
return encrypted
136+
}
137+
138+
/**
139+
* Decrypts an object
140+
*/
141+
private async decrypt<T>(request: string): Promise<T> {
142+
const result = await jose.compactDecrypt(request, this.encryptionKey)
143+
return JSON.parse(new TextDecoder().decode(result.plaintext)) as T
144+
}
145+
146+
async getSsoToken(
129147
tokenSource: TokenSource,
130148
login: boolean = false,
131149
cancellationToken?: CancellationToken
132150
): Promise<GetSsoTokenResult> {
133-
return this.client.sendRequest(
151+
const response: GetSsoTokenResult = await this.client.sendRequest(
134152
getSsoTokenRequestType.method,
135153
{
136154
clientName: this.clientName,
@@ -142,14 +160,17 @@ export class LanguageClientAuth {
142160
} satisfies GetSsoTokenParams,
143161
cancellationToken
144162
)
163+
// Decrypt the access token
164+
response.ssoToken.accessToken = await this.decrypt(response.ssoToken.accessToken)
165+
return response
145166
}
146167

147-
getIamCredential(
168+
async getIamCredential(
148169
profileName: string,
149170
login: boolean = false,
150171
cancellationToken?: CancellationToken
151172
): Promise<GetIamCredentialResult> {
152-
return this.client.sendRequest(
173+
const response: GetIamCredentialResult = await this.client.sendRequest(
153174
getIamCredentialRequestType.method,
154175
{
155176
profileName: profileName,
@@ -159,16 +180,25 @@ export class LanguageClientAuth {
159180
} satisfies GetIamCredentialParams,
160181
cancellationToken
161182
)
183+
// Decrypt the response credentials
184+
const { accessKeyId, secretAccessKey, sessionToken, expiration } = response.credential.credentials
185+
response.credential.credentials = {
186+
accessKeyId: await this.decrypt(accessKeyId),
187+
secretAccessKey: await this.decrypt(secretAccessKey),
188+
sessionToken: sessionToken ? await this.decrypt(sessionToken) : undefined,
189+
expiration: expiration,
190+
}
191+
return response
162192
}
163193

164-
updateSsoProfile(
194+
async updateSsoProfile(
165195
profileName: string,
166196
startUrl: string,
167197
region: string,
168198
scopes: string[]
169199
): Promise<UpdateProfileResult> {
170200
// Add SSO settings and delete credentials from profile
171-
return this.client.sendRequest(updateProfileRequestType.method, {
201+
const params = await this.encrypt({
172202
profile: {
173203
kinds: [ProfileKind.SsoTokenProfile],
174204
name: profileName,
@@ -188,10 +218,11 @@ export class LanguageClientAuth {
188218
sso_registration_scopes: scopes,
189219
},
190220
},
191-
} satisfies UpdateProfileParams)
221+
})
222+
return this.client.sendRequest(updateProfileRequestType.method, params)
192223
}
193224

194-
updateIamProfile(profileName: string, opts: IamProfileOptions): Promise<UpdateProfileResult> {
225+
async updateIamProfile(profileName: string, opts: IamProfileOptions): Promise<UpdateProfileResult> {
195226
// Substitute missing fields for defaults
196227
const fields = { ...IamProfileOptionsDefaults, ...opts }
197228
// Get the profile kind matching the provided fields
@@ -204,7 +235,7 @@ export class LanguageClientAuth {
204235
kind = ProfileKind.Unknown
205236
}
206237

207-
return this.client.sendRequest(updateProfileRequestType.method, {
238+
const params = await this.encrypt({
208239
profile: {
209240
kinds: [kind],
210241
name: profileName,
@@ -217,10 +248,12 @@ export class LanguageClientAuth {
217248
},
218249
},
219250
})
251+
return this.client.sendRequest(updateProfileRequestType.method, params)
220252
}
221253

222-
listProfiles() {
223-
return this.client.sendRequest(listProfilesRequestType.method, {}) as Promise<ListProfilesResult>
254+
async listProfiles() {
255+
const response: string = await this.client.sendRequest(listProfilesRequestType.method, {})
256+
return await this.decrypt<ListProfilesResult>(response)
224257
}
225258

226259
/**
@@ -352,19 +385,6 @@ export abstract class BaseLogin {
352385
this.eventEmitter.fire({ id: this.profileName, state: this.connectionState })
353386
}
354387
}
355-
356-
/**
357-
* Decrypts an encrypted string, removes its quotes, and returns the resulting string
358-
*/
359-
protected async decrypt(encrypted: string): Promise<string> {
360-
try {
361-
const decrypted = await jose.compactDecrypt(encrypted, this.lspAuth.encryptionKey)
362-
return decrypted.plaintext.toString().replaceAll('"', '')
363-
} catch (e) {
364-
getLogger().error(`Failed to decrypt: ${encrypted}`)
365-
return encrypted
366-
}
367-
}
368388
}
369389

370390
/**
@@ -436,9 +456,8 @@ export class SsoLogin extends BaseLogin {
436456
*/
437457
async getCredential() {
438458
const response = await this._getSsoToken(false)
439-
const accessToken = await this.decrypt(response.ssoToken.accessToken)
440459
return {
441-
credential: accessToken,
460+
credential: response.ssoToken.accessToken,
442461
updateCredentialsParams: response.updateCredentialsParams,
443462
}
444463
}
@@ -562,7 +581,7 @@ export class IamLogin extends BaseLogin {
562581
sourceProfile: sourceProfile,
563582
})
564583
} else {
565-
// Create the target profile
584+
// Create the credentials profile
566585
await this.lspAuth.updateIamProfile(this.profileName, {
567586
accessKey: opts.accessKey,
568587
secretKey: opts.secretKey,
@@ -575,14 +594,6 @@ export class IamLogin extends BaseLogin {
575594
* Restore the connection state and connection details to memory, if they exist.
576595
*/
577596
async restore() {
578-
const sessionData = await this.getProfile()
579-
const credentials = sessionData?.profile?.settings
580-
if (credentials?.aws_access_key_id && credentials?.aws_secret_access_key) {
581-
this._data = {
582-
accessKey: credentials.aws_access_key_id,
583-
secretKey: credentials.aws_secret_access_key,
584-
}
585-
}
586597
try {
587598
await this._getIamCredential(false)
588599
} catch (err) {
@@ -596,15 +607,8 @@ export class IamLogin extends BaseLogin {
596607
*/
597608
async getCredential() {
598609
const response = await this._getIamCredential(false)
599-
const credentials: IamCredentials = {
600-
accessKeyId: await this.decrypt(response.credential.credentials.accessKeyId),
601-
secretAccessKey: await this.decrypt(response.credential.credentials.secretAccessKey),
602-
sessionToken: response.credential.credentials.sessionToken
603-
? await this.decrypt(response.credential.credentials.sessionToken)
604-
: undefined,
605-
}
606610
return {
607-
credential: credentials,
611+
credential: response.credential.credentials,
608612
updateCredentialsParams: response.updateCredentialsParams,
609613
}
610614
}
@@ -639,7 +643,7 @@ export class IamLogin extends BaseLogin {
639643
}
640644

641645
// Update cached credentials and credential ID
642-
if (response.credential?.credentials?.accessKeyId && response.credential?.credentials?.secretAccessKey) {
646+
if (response.credential.credentials.accessKeyId && response.credential.credentials.secretAccessKey) {
643647
this._data = {
644648
accessKey: response.credential.credentials.accessKeyId,
645649
secretKey: response.credential.credentials.secretAccessKey,

0 commit comments

Comments
 (0)