Skip to content

Commit eb9edd8

Browse files
committed
Refactor, fixes
1 parent fcb68e3 commit eb9edd8

File tree

6 files changed

+142
-119
lines changed

6 files changed

+142
-119
lines changed

src/auth/AuthProvider/index.ts

Lines changed: 47 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,32 @@ import {
1313
WebviewView
1414
} from "vscode";
1515
import { v4 as uuid } from "uuid";
16-
import { PromiseAdapter, promiseFromEvent } from "./utils/promiseFromEvent";
16+
import { promiseFromEvent } from "./utils/promiseFromEvent";
1717
import fetch from "node-fetch";
1818
import UriEventHandler from "./utils/UriEventHandler";
19-
import { ExchangePromise } from "./types";
19+
import {
20+
AuthSession,
21+
ExchangePromise,
22+
User,
23+
Auth0LoginType,
24+
OnReturn,
25+
ResponseAuth0,
26+
UserInfoAuth0
27+
} from "./types";
2028
import {
2129
fetchPlatformData,
2230
clearPlatformData
2331
} from "../../webview/WebviewProvider/lib";
2432
import {
2533
AUTH0_CLIENT_ID,
2634
AUTH0_CLIENT_SECRET,
35+
AUTH0_DOMAIN,
2736
AUTH0_SCOPES
2837
} from "../../constants";
2938

3039
const TYPE = `auth0`;
3140
const NAME = `Seqera Cloud`;
32-
const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`;
33-
export const SESSIONS_SECRET_KEY = `${TYPE}.sessions`;
34-
35-
type ResponseAuth0 = {
36-
access_token: string;
37-
refresh_token?: string;
38-
token_type: "Bearer";
39-
expires_in: number;
40-
scope: string;
41-
id_token: string;
42-
};
43-
44-
type UserInfoAuth0 = {
45-
email: string;
46-
email_verified: boolean;
47-
family_name: string;
48-
given_name: string;
49-
name: string;
50-
nickname: string;
51-
picture: string;
52-
preferred_username: string;
53-
sub: string;
54-
updated_at: string;
55-
};
56-
57-
type Auth0LoginType = "code" | "token";
58-
59-
type OnReturn = PromiseAdapter<Uri, string>;
41+
export const STORAGE_KEY = `${TYPE}.sessions`;
6042

6143
class AuthProvider implements AuthenticationProvider, Disposable {
6244
private eventEmitter = new EventEmitter<ChangeEvent>();
@@ -79,14 +61,15 @@ class AuthProvider implements AuthenticationProvider, Disposable {
7961
}
8062

8163
public async getSessions(): Promise<AuthenticationSession[]> {
82-
const allSessions = await this.context.secrets.get(SESSIONS_SECRET_KEY);
64+
const allSessions = await this.context.secrets.get(STORAGE_KEY);
8365
if (!allSessions) return [];
8466
return JSON.parse(allSessions) as AuthenticationSession[];
8567
}
8668

87-
public async createSession(scopes: string[]): Promise<AuthenticationSession> {
69+
public async createSession(): Promise<AuthenticationSession> {
8870
try {
8971
let accessToken;
72+
let refreshToken;
9073

9174
if (AUTH0_CLIENT_SECRET) {
9275
// Note: this "code" response type is for allowing token refresh functionality. getting a
@@ -96,6 +79,7 @@ class AuthProvider implements AuthenticationProvider, Disposable {
9679
if (!code) throw new Error(`Auth0 login failure (code flow)`);
9780
const auth0Response = await this.fetchAuth0Tokens(code);
9881
accessToken = auth0Response.access_token;
82+
refreshToken = auth0Response.refresh_token;
9983
} else {
10084
accessToken = await this.startLogin("token");
10185
if (!accessToken) throw new Error(`Auth0 login failure (token flow)`);
@@ -111,26 +95,8 @@ class AuthProvider implements AuthenticationProvider, Disposable {
11195
if (!user) throw new Error(`User not found`);
11296

11397
// If that worked, store the vscode session
114-
const session: AuthenticationSession = {
115-
id: uuid(),
116-
accessToken,
117-
account: {
118-
label: user?.userName,
119-
id: user.email
120-
},
121-
scopes: []
122-
};
123-
124-
await this.context.secrets.store(
125-
SESSIONS_SECRET_KEY,
126-
JSON.stringify([session])
127-
);
128-
129-
this.eventEmitter.fire({
130-
added: [session],
131-
removed: [],
132-
changed: []
133-
});
98+
const session = await this.storeSession(user, accessToken, refreshToken);
99+
if (!session) throw new Error(`Failed to store new session`);
134100

135101
return session;
136102
} catch (e) {
@@ -139,18 +105,42 @@ class AuthProvider implements AuthenticationProvider, Disposable {
139105
}
140106
}
141107

108+
private async storeSession(
109+
user: User,
110+
accessToken: string,
111+
refreshToken?: string
112+
) {
113+
const session: AuthSession = {
114+
id: uuid(),
115+
accessToken,
116+
refreshToken,
117+
account: {
118+
label: user.userName,
119+
id: user.email
120+
},
121+
scopes: []
122+
};
123+
124+
await this.context.secrets.store(STORAGE_KEY, JSON.stringify([session]));
125+
126+
this.eventEmitter.fire({
127+
added: [session],
128+
removed: [],
129+
changed: []
130+
});
131+
132+
return session;
133+
}
134+
142135
public async removeSession(sessionId: string): Promise<void> {
143-
const allSessions = await this.context.secrets.get(SESSIONS_SECRET_KEY);
136+
const allSessions = await this.context.secrets.get(STORAGE_KEY);
144137
if (allSessions) {
145138
let sessions = JSON.parse(allSessions) as AuthenticationSession[];
146139
const sessionIdx = sessions.findIndex((s) => s.id === sessionId);
147140
const session = sessions[sessionIdx];
148141
sessions.splice(sessionIdx, 1);
149142

150-
await this.context.secrets.store(
151-
SESSIONS_SECRET_KEY,
152-
JSON.stringify(sessions)
153-
);
143+
await this.context.secrets.store(STORAGE_KEY, JSON.stringify(sessions));
154144

155145
clearPlatformData(this.webviewView, this.context);
156146

@@ -275,16 +265,6 @@ class AuthProvider implements AuthenticationProvider, Disposable {
275265
return res;
276266
}
277267

278-
private async fetchAuth0User(token: string): Promise<UserInfoAuth0> {
279-
const response = await fetch(`https://${AUTH0_DOMAIN}/userinfo`, {
280-
headers: {
281-
Authorization: `Bearer ${token}`
282-
}
283-
});
284-
const res = (await response.json()) as UserInfoAuth0;
285-
return res;
286-
}
287-
288268
public setWebview(webview: any) {
289269
this.webviewView = webview;
290270
}

src/auth/AuthProvider/types.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { EventEmitter } from "vscode";
1+
import { AuthenticationSession, EventEmitter, Uri } from "vscode";
2+
import { PromiseAdapter } from "./utils/promiseFromEvent";
23

34
export type User = {
45
id: number;
@@ -35,3 +36,32 @@ export type ExchangePromise = {
3536
promise: Promise<string>;
3637
cancel: EventEmitter<void>;
3738
};
39+
40+
export type AuthSession = AuthenticationSession & {
41+
refreshToken?: string;
42+
};
43+
44+
export type ResponseAuth0 = {
45+
access_token: string;
46+
refresh_token?: string;
47+
token_type: "Bearer";
48+
expires_in: number;
49+
scope: string;
50+
id_token: string;
51+
};
52+
53+
export type UserInfoAuth0 = {
54+
email: string;
55+
email_verified: boolean;
56+
family_name: string;
57+
given_name: string;
58+
name: string;
59+
nickname: string;
60+
picture: string;
61+
preferred_username: string;
62+
sub: string;
63+
updated_at: string;
64+
};
65+
66+
export type Auth0LoginType = "code" | "token";
67+
export type OnReturn = PromiseAdapter<Uri, string>;

src/auth/getAccessToken.ts

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { AuthenticationSession, ExtensionContext } from "vscode";
1+
import { ExtensionContext } from "vscode";
22
import { jwtExpired } from "./AuthProvider/utils/jwt";
3-
import fetch from "node-fetch";
43

5-
import { SESSIONS_SECRET_KEY } from "./AuthProvider";
6-
import { AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET } from "../constants";
4+
import { STORAGE_KEY } from "./AuthProvider";
5+
import refreshAccessToken from "./refreshAccessToken";
6+
import { AuthSession } from "./AuthProvider/types";
77

8-
type SessionWithRefresh = AuthenticationSession & { refreshToken?: string };
9-
type Auth0TokenResponse = {
8+
export type Auth0TokenResponse = {
109
access_token: string;
1110
refresh_token?: string;
1211
expires_in: number;
@@ -17,52 +16,15 @@ type Auth0TokenResponse = {
1716
const getAccessToken = async (
1817
context: ExtensionContext
1918
): Promise<string | undefined> => {
20-
const sessionsStr = await context.secrets.get(SESSIONS_SECRET_KEY);
19+
const sessionsStr = await context.secrets.get(STORAGE_KEY);
2120
const sessions = sessionsStr ? JSON.parse(sessionsStr) : [];
22-
const session = sessions[0] as SessionWithRefresh;
21+
const session = sessions[0] as AuthSession;
2322
let token = session?.accessToken;
2423

2524
// Check if token is expired
2625
if (token && jwtExpired(token)) {
27-
// 🟢 Token expired, try to refresh
28-
const refreshToken = session.refreshToken;
29-
if (refreshToken) {
30-
try {
31-
const data = new URLSearchParams([
32-
["grant_type", "refresh_token"],
33-
["client_id", AUTH0_CLIENT_ID],
34-
["client_secret", AUTH0_CLIENT_SECRET],
35-
["refresh_token", refreshToken]
36-
]);
37-
const response = await fetch(
38-
`https://seqera-development.eu.auth0.com/oauth/token`,
39-
{
40-
method: "POST",
41-
headers: { "content-type": "application/x-www-form-urlencoded" },
42-
body: data.toString()
43-
}
44-
);
45-
const tokens = (await response.json()) as Auth0TokenResponse;
46-
if (tokens.access_token) {
47-
// 🟢 Refreshed access token
48-
// Update the session object
49-
const updatedSession: SessionWithRefresh = {
50-
...session,
51-
accessToken: tokens.access_token,
52-
refreshToken: tokens.refresh_token || session.refreshToken
53-
};
54-
// Save updated session
55-
await context.secrets.store(
56-
SESSIONS_SECRET_KEY,
57-
JSON.stringify([updatedSession])
58-
);
59-
token = tokens.access_token;
60-
}
61-
} catch (err) {
62-
console.log("🟢 Failed to refresh token", err);
63-
// Optionally: clear session or prompt re-login
64-
}
65-
}
26+
const newToken = await refreshAccessToken(session, context);
27+
if (newToken) return newToken;
6628
}
6729
return token;
6830
};

src/auth/refreshAccessToken.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// import fetch from "node-fetch";
2+
3+
import { STORAGE_KEY } from "./AuthProvider";
4+
import {
5+
AUTH0_CLIENT_ID,
6+
AUTH0_CLIENT_SECRET,
7+
AUTH0_DOMAIN
8+
} from "../constants";
9+
import { Auth0TokenResponse } from "./getAccessToken";
10+
import { ExtensionContext } from "vscode";
11+
import { AuthSession } from "./AuthProvider/types";
12+
13+
const refreshAccessToken = async (
14+
session: AuthSession,
15+
context: ExtensionContext
16+
): Promise<string | undefined> => {
17+
const refreshToken = session.refreshToken;
18+
if (!refreshToken) return undefined;
19+
20+
try {
21+
const data = new URLSearchParams([
22+
["grant_type", "refresh_token"],
23+
["client_id", AUTH0_CLIENT_ID],
24+
["client_secret", AUTH0_CLIENT_SECRET],
25+
["refresh_token", refreshToken]
26+
]);
27+
28+
const response = await fetch(`${AUTH0_DOMAIN}/oauth/token`, {
29+
method: "POST",
30+
headers: { "content-type": "application/x-www-form-urlencoded" },
31+
body: data.toString()
32+
});
33+
34+
const tokens = (await response.json()) as Auth0TokenResponse;
35+
const { access_token, refresh_token } = tokens;
36+
if (!access_token) throw new Error(`No new access token`);
37+
38+
const updatedSession: AuthSession = {
39+
...session,
40+
accessToken: access_token,
41+
refreshToken: refresh_token || session.refreshToken
42+
};
43+
await context.secrets.store(STORAGE_KEY, JSON.stringify([updatedSession]));
44+
return access_token;
45+
} catch (err) {
46+
console.log("🔴 Failed to refresh token", err);
47+
return undefined;
48+
}
49+
return undefined;
50+
};
51+
52+
export default refreshAccessToken;

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export const AUTH0_CLIENT_SECRET =
1111
"tZ3N8vHuvpLQlzdGEhel4Vz5DeluNNyTtid-2jFBdDiXmIGNbX9yhjDmQ2Pg6VT-";
1212
export const AUTH0_CLIENT_ID = "7PJnvIXiXK3HkQR43c4zBf3bWuxISp9W";
1313
export const AUTH0_SCOPES = "openid profile email offline_access";
14+
export const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`;

src/webview/WebviewProvider/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ class WebviewProvider implements vscode.WebviewViewProvider {
153153

154154
public async initViewData(refresh?: boolean) {
155155
const { viewID, _context, _currentView: view } = this;
156-
console.log("🟠 initViewData", viewID);
157156
if (!view) return;
158157
if (viewID === "seqeraCloud") {
159158
this.getRepoInfo();
@@ -183,7 +182,7 @@ class WebviewProvider implements vscode.WebviewViewProvider {
183182
const created = await createTest(filePath, accessToken);
184183
this.emitTestCreated(filePath, created);
185184
} catch (error) {
186-
console.log("🟠 Test creation failed", error);
185+
console.log("🔴 Test creation failed", error);
187186
this.emitTestCreated(filePath, false);
188187
}
189188
}
@@ -199,12 +198,11 @@ class WebviewProvider implements vscode.WebviewViewProvider {
199198

200199
private async getContainer(filePath: string) {
201200
const accessToken = await getAccessToken(this._context);
202-
203201
try {
204202
const created = await getContainer(filePath, accessToken);
205203
this.emitContainerCreated(filePath, created);
206204
} catch (error) {
207-
console.log("🟠 Container creation failed", error);
205+
console.log("🔴 Container creation failed", error);
208206
this.emitContainerCreated(filePath, false);
209207
}
210208
}

0 commit comments

Comments
 (0)