Skip to content

Commit 499ab70

Browse files
authored
feat(auth): add minimal signout button (#2970)
## Problem No UI to signout ## Solution Add a button on the node. This is a placeholder to make development easier and is not the final UI.
1 parent d01a5d5 commit 499ab70

File tree

3 files changed

+38
-18
lines changed

3 files changed

+38
-18
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,11 @@
17171717
"command": "aws.cdk.viewDocs",
17181718
"when": "viewItem == awsCdkRootNode",
17191719
"group": "0@2"
1720+
},
1721+
{
1722+
"command": "aws.auth.logout",
1723+
"when": "viewItem == awsAuthNode",
1724+
"group": "inline@1"
17201725
}
17211726
]
17221727
},
@@ -1814,6 +1819,11 @@
18141819
}
18151820
}
18161821
},
1822+
{
1823+
"command": "aws.auth.logout",
1824+
"title": "%AWS.command.logout%",
1825+
"category": "%AWS.title%"
1826+
},
18171827
{
18181828
"command": "aws.createIssueOnGitHub",
18191829
"title": "%AWS.command.createIssueOnGitHub%",

src/credentials/auth.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ export class Auth implements AuthService, ConnectionManager {
356356
return connections.find(c => c.id === connection.id)
357357
}
358358

359+
/**
360+
* Attempts to remove all auth state related to the connection.
361+
*
362+
* For SSO, this involves an API call to clear server-side state. The call happens
363+
* before the local token(s) are cleared as they are needed in the request.
364+
*/
359365
private async invalidateConnection(id: Connection['id']) {
360366
const profile = this.store.getProfileOrThrow(id)
361367

@@ -482,6 +488,7 @@ export async function promptLogin(auth: Auth) {
482488
}
483489

484490
const loginCommand = Commands.register('aws.auth.login', promptLogin)
491+
Commands.register('aws.auth.logout', () => Auth.instance.logout())
485492

486493
function mapEventType<T, U = void>(event: vscode.Event<T>, fn?: (val: T) => U): vscode.Event<U> {
487494
const emitter = new vscode.EventEmitter<U>()
@@ -508,6 +515,7 @@ export class AuthNode {
508515
const item = new vscode.TreeItem(itemLabel)
509516
item.iconPath = getIcon('vscode-account')
510517
item.command = loginCommand.build(this.resource).asCommand({ title: 'Login' })
518+
item.contextValue = 'awsAuthNode'
511519

512520
return item
513521
}

src/credentials/sso/clients.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,24 +110,30 @@ export class OidcClient {
110110
}
111111

112112
type OmittedProps = 'accessToken' | 'nextToken'
113-
type ExtractOverload<T> = T extends {
114-
(this: void, ...args: infer P1): infer R1
115-
(this: void, ...args: infer P2): infer R2
116-
(this: void, ...args: infer P3): infer R3
113+
type ExtractOverload<T, U> = T extends {
114+
(...args: infer P1): infer R1
115+
(...args: infer P2): infer R2
116+
(...args: infer P3): infer R3
117117
}
118-
? (...args: P1) => R1
118+
? (this: U, ...args: P1) => R1
119119
: never
120120

121+
// Removes all methods that use callbacks instead of promises
122+
type PromisifyClient<T> = {
123+
[P in keyof T]: T[P] extends (...args: any[]) => any ? ExtractOverload<T[P], PromisifyClient<T>> : T[P]
124+
}
125+
121126
export class SsoClient {
122-
public constructor(private readonly client: SSO, private readonly provider: SsoAccessTokenProvider) {}
127+
public constructor(
128+
private readonly client: PromisifyClient<SSO>,
129+
private readonly provider: SsoAccessTokenProvider
130+
) {}
123131

124132
public listAccounts(
125133
request: Omit<ListAccountsRequest, OmittedProps> = {}
126134
): AsyncCollection<RequiredProps<AccountInfo, 'accountId'>[]> {
127-
const method = this.client.listAccounts.bind(this.client)
128135
const requester = (request: Omit<ListAccountsRequest, 'accessToken'>) =>
129-
this.call(method as ExtractOverload<typeof method>, request)
130-
136+
this.call(this.client.listAccounts, request)
131137
const collection = pageableToCollection(requester, request, 'nextToken', 'accountList')
132138

133139
return collection.filter(isNonNullable).map(accounts => accounts.map(a => (assertHasProps(a, 'accountId'), a)))
@@ -136,10 +142,8 @@ export class SsoClient {
136142
public listAccountRoles(
137143
request: Omit<ListAccountRolesRequest, OmittedProps>
138144
): AsyncCollection<Required<RoleInfo>[]> {
139-
const method = this.client.listAccountRoles.bind(this.client)
140145
const requester = (request: Omit<ListAccountRolesRequest, 'accessToken'>) =>
141-
this.call(method as ExtractOverload<typeof method>, request)
142-
146+
this.call(this.client.listAccountRoles, request)
143147
const collection = pageableToCollection(requester, request, 'nextToken', 'roleList')
144148

145149
return collection
@@ -148,8 +152,7 @@ export class SsoClient {
148152
}
149153

150154
public async getRoleCredentials(request: Omit<GetRoleCredentialsRequest, OmittedProps>) {
151-
const method = this.client.getRoleCredentials.bind(this.client)
152-
const response = await this.call(method as ExtractOverload<typeof method>, request)
155+
const response = await this.call(this.client.getRoleCredentials, request)
153156

154157
assertHasProps(response, 'roleCredentials')
155158
assertHasProps(response.roleCredentials, 'accessKeyId', 'secretAccessKey')
@@ -163,20 +166,19 @@ export class SsoClient {
163166
}
164167

165168
public async logout(request: Omit<LogoutRequest, OmittedProps> = {}) {
166-
const method = this.client.logout.bind(this.client)
167-
await this.call(method as ExtractOverload<typeof method>, request)
169+
await this.call(this.client.logout, request)
168170
}
169171

170172
private call<T extends { accessToken: string | undefined }, U>(
171-
method: (request: T) => Promise<U>,
173+
method: (this: typeof this.client, request: T) => Promise<U>,
172174
request: Omit<T, 'accessToken'>
173175
): Promise<U> {
174176
const requester = async (req: T) => {
175177
const token = await this.provider.getToken()
176178
assertHasProps(token, 'accessToken')
177179

178180
try {
179-
return await method({ ...req, accessToken: token.accessToken })
181+
return await method.call(this.client, { ...req, accessToken: token.accessToken })
180182
} catch (error) {
181183
await this.handleError(error)
182184
throw error

0 commit comments

Comments
 (0)