From 214b4fcbfc07f3ac840f3ee05c791d0957e993a3 Mon Sep 17 00:00:00 2001 From: Jake Broughton Date: Mon, 19 May 2025 15:16:12 +0200 Subject: [PATCH 01/11] Gets code value successfully --- src/auth/AuthProvider/index.ts | 292 ++++++++++++++++++++++----------- src/auth/getAccessToken.ts | 4 +- 2 files changed, 197 insertions(+), 99 deletions(-) diff --git a/src/auth/AuthProvider/index.ts b/src/auth/AuthProvider/index.ts index bbff4f78..0e0f9265 100644 --- a/src/auth/AuthProvider/index.ts +++ b/src/auth/AuthProvider/index.ts @@ -1,100 +1,126 @@ -import { v4 as uuid } from "uuid"; import { - authentication as vscodeAuth, + authentication, + AuthenticationProvider, + AuthenticationProviderAuthenticationSessionsChangeEvent, + AuthenticationSession, + Disposable, env, - window, EventEmitter, - Disposable, + ExtensionContext, ProgressLocation, - Uri + Uri, + UriHandler, + window } from "vscode"; - +import { v4 as uuid } from "uuid"; import { PromiseAdapter, promiseFromEvent } from "./utils/promiseFromEvent"; -import UriEventHandler from "./utils/UriEventHandler"; -import { fetchPlatformData } from "../../webview/WebviewProvider/lib"; -import { SEQERA_PLATFORM_URL } from "../../constants"; - -import type { - AuthenticationProvider, - AuthenticationProviderAuthenticationSessionsChangeEvent as ChangeEvent, - AuthenticationSession, - ExtensionContext, - WebviewView -} from "vscode"; - -type ExchangePromise = { - promise: Promise; - cancel: EventEmitter; -}; - -const TYPE = `auth0`; -const NAME = `Seqera Cloud`; -const AUTH_ENDPOINT = `${SEQERA_PLATFORM_URL}/oauth/login/auth0?source=vscode`; -export const STORAGE_KEY_NAME = `${TYPE}.sessions`; - -class AuthProvider implements AuthenticationProvider, Disposable { - private uriHandler = new UriEventHandler(); - private eventEmitter = new EventEmitter(); - private currentInstance: Disposable; - private pendingIDs: string[] = []; // TODO: Does this do anything? - private promises = new Map(); - private webviewView!: WebviewView["webview"]; +import fetch from "node-fetch"; + +export const AUTH_TYPE = `auth0`; +const AUTH_NAME = `Auth0`; +const CLIENT_ID = `7PJnvIXiXK3HkQR43c4zBf3bWuxISp9W`; +var CLIENT_SECRET = + "tZ3N8vHuvpLQlzdGEhel4Vz5DeluNNyTtid-2jFBdDiXmIGNbX9yhjDmQ2Pg6VT-"; +const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`; +const PLATFORM_DOMAIN = `pr-8246.dev-tower.net`; +export const SESSIONS_SECRET_KEY = `${AUTH_TYPE}.sessions`; + +class UriEventHandler extends EventEmitter implements UriHandler { + public handleUri(uri: Uri) { + this.fire(uri); + } +} +class Auth0AuthenticationProvider + implements AuthenticationProvider, Disposable +{ + private _sessionChangeEmitter = + new EventEmitter(); + private _disposable: Disposable; + private _pendingStates: string[] = []; + private _codeExchangePromises = new Map< + string, + { promise: Promise; cancel: EventEmitter } + >(); + private _uriHandler = new UriEventHandler(); + private webviewView: any; constructor(private readonly context: ExtensionContext) { - const { registerAuthenticationProvider: register } = vscodeAuth; - const options = { supportsMultipleAccounts: false }; - const authInstance = register(TYPE, NAME, this, options); - const uriHandler = window.registerUriHandler(this.uriHandler); - this.currentInstance = Disposable.from(authInstance, uriHandler); + this._disposable = Disposable.from( + authentication.registerAuthenticationProvider( + AUTH_TYPE, + AUTH_NAME, + // @ts-ignore + this, + { supportsMultipleAccounts: false } + ), + window.registerUriHandler(this._uriHandler) + ); } get onDidChangeSessions() { - return this.eventEmitter.event; + return this._sessionChangeEmitter.event; } get redirectUri() { const publisher = this.context.extension.packageJSON.publisher; const name = this.context.extension.packageJSON.name; - return `${env.uriScheme}://${publisher}.${name}`; + return `vscode://${publisher}.${name}`; } - public async getSessions(): Promise { - const sessions = await this.context.secrets.get(STORAGE_KEY_NAME); - if (!sessions) return []; - return JSON.parse(sessions) as AuthenticationSession[]; + // @ts-ignore + public async getSessions( + scopes?: string[] + ): Promise { + const allSessions = await this.context.secrets.get(SESSIONS_SECRET_KEY); + if (allSessions) { + return Object.freeze(JSON.parse(allSessions) as AuthenticationSession[]); + } + return []; } public async createSession(scopes: string[]): Promise { try { - const accessToken = await this.login(scopes); - if (!accessToken) { - throw new Error(`Platform login failure`); + var token: string = ""; + if (CLIENT_SECRET) { + const code = await this.login(scopes, "code"); + if (!code) { + throw new Error(`Auth0 login failure`); + } + const token_response = (await this.getToken(code)) as { + access_token: string; + token_type: string; + }; + console.log("🉑 token_response", token_response); + token = token_response.access_token; + } else { + token = await this.login(scopes, "token"); + if (!token) { + throw new Error(`Auth0 login failure`); + } } - - const data = await fetchPlatformData( - accessToken, - this.webviewView, - this.context - ); - const { userInfo } = data; - const account = { - label: userInfo?.user?.userName || "Undefined", - id: userInfo?.user?.email || "undefined" - }; + const userinfo = (await this.getUserInfo(token)) as any; + // const puserinfo = (await this.getPlatformUserInfo(token)) as { + // user: { id: string; userName: string; email: string }; + // }; + // const platformuser: { id: string; userName: string; email: string } = + // puserinfo.user; const session: AuthenticationSession = { id: uuid(), - accessToken, - account, + accessToken: token, + account: { + label: userinfo.name + "(seqera: " + userinfo.nickname + " )", + id: userinfo.email + }, scopes: [] }; await this.context.secrets.store( - STORAGE_KEY_NAME, + SESSIONS_SECRET_KEY, JSON.stringify([session]) ); - this.eventEmitter.fire({ + this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] @@ -108,46 +134,43 @@ class AuthProvider implements AuthenticationProvider, Disposable { } public async removeSession(sessionId: string): Promise { - const allSessions = await this.context.secrets.get(STORAGE_KEY_NAME); - if (!allSessions) return; - let sessions = JSON.parse(allSessions) as AuthenticationSession[]; - const sessionIdx = sessions.findIndex((s) => s.id === sessionId); - const session = sessions[sessionIdx]; - sessions.splice(sessionIdx, 1); - - await this.context.secrets.store( - STORAGE_KEY_NAME, - JSON.stringify(sessions) - ); + const allSessions = await this.context.secrets.get(SESSIONS_SECRET_KEY); + if (allSessions) { + let sessions = JSON.parse(allSessions) as AuthenticationSession[]; + const sessionIdx = sessions.findIndex((s) => s.id === sessionId); + const session = sessions[sessionIdx]; + sessions.splice(sessionIdx, 1); - this.webviewView?.postMessage({ authState: {} }); - const vsCodeState = this.context.workspaceState; - vsCodeState.update("platformData", {}); + await this.context.secrets.store( + SESSIONS_SECRET_KEY, + JSON.stringify(sessions) + ); - if (session) { - this.eventEmitter.fire({ - added: [], - removed: [session], - changed: [] - }); + if (session) { + this._sessionChangeEmitter.fire({ + added: [], + removed: [session], + changed: [] + }); + } } } public async dispose() { - this.currentInstance.dispose(); + this._disposable.dispose(); } - private async login(scopes: string[] = []) { + private async login(scopes: string[] = [], response_type: string) { return await window.withProgress( { location: ProgressLocation.Notification, - title: "Signing in to Seqera Cloud...", + title: "Signing in to Auth0...", cancellable: true }, async (_, token) => { const stateId = uuid(); - this.pendingIDs.push(stateId); + this._pendingStates.push(stateId); const scopeString = scopes.join(" "); @@ -160,17 +183,33 @@ class AuthProvider implements AuthenticationProvider, Disposable { if (!scopes.includes("email")) { scopes.push("email"); } + if (!scopes.includes("offline_access")) { + scopes.push("offline_access"); + } - const uri = Uri.parse(AUTH_ENDPOINT); + console.log("🟠 login scopes", scopes); + + const searchParams = new URLSearchParams([ + ["response_type", response_type], + ["client_id", CLIENT_ID], + ["redirect_uri", this.redirectUri], + ["state", stateId], + ["scope", scopes.join(" ")], + ["audience", "platform"], + ["prompt", "login"] + ]); + const uri = Uri.parse( + `https://${AUTH0_DOMAIN}/authorize?${searchParams.toString()}` + ); await env.openExternal(uri); - let codeExchangePromise = this.promises.get(scopeString); + let codeExchangePromise = this._codeExchangePromises.get(scopeString); if (!codeExchangePromise) { codeExchangePromise = promiseFromEvent( - this.uriHandler.event, + this._uriHandler.event, this.handleUri(scopes) ); - this.promises.set(scopeString, codeExchangePromise); + this._codeExchangePromises.set(scopeString, codeExchangePromise); } try { @@ -187,9 +226,11 @@ class AuthProvider implements AuthenticationProvider, Disposable { ).promise ]); } finally { - this.pendingIDs = this.pendingIDs.filter((n) => n !== stateId); + this._pendingStates = this._pendingStates.filter( + (n) => n !== stateId + ); codeExchangePromise?.cancel.fire(); - this.promises.delete(scopeString); + this._codeExchangePromises.delete(scopeString); } } ); @@ -199,20 +240,77 @@ class AuthProvider implements AuthenticationProvider, Disposable { scopes: readonly string[] ) => PromiseAdapter = (scopes) => async (uri, resolve, reject) => { - const query = new URLSearchParams(uri.fragment); - const accessToken = query.get("access_token"); + const query = uri.query + ? new URLSearchParams(uri.query) + : new URLSearchParams(uri.fragment); + const accessToken = query.get("code") || query.get("access_token"); + const state = query.get("state"); + console.log("🟢 handleUri uri", uri); + console.log("🟢 handleUri query", query); + console.log("🟢 handleUri code", query.get("code")); + console.log("🟢 handleUri access_token", query.get("access_token")); + console.log("🟢 handleUri state", query.get("state")); if (!accessToken) { reject(new Error("No token")); return; } + if (!state) { + reject(new Error("No state")); + return; + } + + if (!this._pendingStates.some((n) => n === state)) { + reject(new Error("State not found")); + return; + } resolve(accessToken); }; + private async getToken(code: string) { + const data = new URLSearchParams([ + ["grant_type", "authorization_code"], + ["client_id", CLIENT_ID], + ["client_secret", CLIENT_SECRET], + ["code", code], + ["redirect_uri", this.redirectUri] + ]); + const auth = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, { + method: `POST`, + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: data.toString() + }); + const res = await auth.json(); + console.log("🉑🉑🉑🉑 auth", res); + return res; + } + + private async getUserInfo(token: string) { + console.log("🉑 getUserInfo", token); + const response = await fetch(`https://${AUTH0_DOMAIN}/userinfo`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + const res = await response.json(); + console.log("🉑 getUserInfo", res); + return res; + } + + private async getPlatformUserInfo(token: string) { + const response = await fetch(`https://${PLATFORM_DOMAIN}/api/user-info`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + console.log("🉑 getPlatformUserInfo", response); + return await response.json(); + } + public setWebview(webview: any) { this.webviewView = webview; } } -export default AuthProvider; +export default Auth0AuthenticationProvider; diff --git a/src/auth/getAccessToken.ts b/src/auth/getAccessToken.ts index cd14da3f..fea32171 100644 --- a/src/auth/getAccessToken.ts +++ b/src/auth/getAccessToken.ts @@ -1,11 +1,11 @@ import { AuthenticationSession, ExtensionContext } from "vscode"; -import { STORAGE_KEY_NAME } from "./AuthProvider"; +import { SESSIONS_SECRET_KEY } from "./AuthProvider"; const getAccessToken = async ( context: ExtensionContext ): Promise => { - const sessionsStr = await context.secrets.get(STORAGE_KEY_NAME); + const sessionsStr = await context.secrets.get(SESSIONS_SECRET_KEY); const sessions = sessionsStr ? JSON.parse(sessionsStr) : []; const session = sessions[0] as AuthenticationSession; const token = session?.accessToken; From 6f874b7c1ba60089913e78d643339a570e007bca Mon Sep 17 00:00:00 2001 From: Jake Broughton Date: Mon, 19 May 2025 16:27:22 +0200 Subject: [PATCH 02/11] Partially working state --- src/auth/AuthProvider/index.ts | 34 ++++++++++++------- src/constants.ts | 3 +- .../lib/platform/utils/fetchUserInfo.ts | 3 +- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/auth/AuthProvider/index.ts b/src/auth/AuthProvider/index.ts index 0e0f9265..efce62ab 100644 --- a/src/auth/AuthProvider/index.ts +++ b/src/auth/AuthProvider/index.ts @@ -15,6 +15,7 @@ import { import { v4 as uuid } from "uuid"; import { PromiseAdapter, promiseFromEvent } from "./utils/promiseFromEvent"; import fetch from "node-fetch"; +import { SEQERA_API_URL } from "../../constants"; export const AUTH_TYPE = `auth0`; const AUTH_NAME = `Auth0`; @@ -22,9 +23,17 @@ const CLIENT_ID = `7PJnvIXiXK3HkQR43c4zBf3bWuxISp9W`; var CLIENT_SECRET = "tZ3N8vHuvpLQlzdGEhel4Vz5DeluNNyTtid-2jFBdDiXmIGNbX9yhjDmQ2Pg6VT-"; const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`; -const PLATFORM_DOMAIN = `pr-8246.dev-tower.net`; export const SESSIONS_SECRET_KEY = `${AUTH_TYPE}.sessions`; +type AUTH0_OAUTH_RESPONSE = { + access_token: string; + refresh_token?: string; + token_type: "Bearer"; + expires_in: number; + scope: string; + id_token: string; +}; + class UriEventHandler extends EventEmitter implements UriHandler { public handleUri(uri: Uri) { this.fire(uri); @@ -82,18 +91,17 @@ class Auth0AuthenticationProvider try { var token: string = ""; if (CLIENT_SECRET) { - const code = await this.login(scopes, "code"); + // Note: for getting a refresh token, we need to use this "code" flow. + // Use the Auth0 app's secret, and ensure "Allow Offline Access" is enabled. + const code = await this.startLogin(scopes, "code"); if (!code) { throw new Error(`Auth0 login failure`); } - const token_response = (await this.getToken(code)) as { - access_token: string; - token_type: string; - }; - console.log("🉑 token_response", token_response); - token = token_response.access_token; + const auth0Response = await this.getToken(code); + console.log("🉑 auth0Response", auth0Response); + token = auth0Response.access_token; } else { - token = await this.login(scopes, "token"); + token = await this.startLogin(scopes, "token"); if (!token) { throw new Error(`Auth0 login failure`); } @@ -160,7 +168,7 @@ class Auth0AuthenticationProvider this._disposable.dispose(); } - private async login(scopes: string[] = [], response_type: string) { + private async startLogin(scopes: string[] = [], response_type: string) { return await window.withProgress( { location: ProgressLocation.Notification, @@ -268,7 +276,7 @@ class Auth0AuthenticationProvider resolve(accessToken); }; - private async getToken(code: string) { + private async getToken(code: string): Promise { const data = new URLSearchParams([ ["grant_type", "authorization_code"], ["client_id", CLIENT_ID], @@ -281,7 +289,7 @@ class Auth0AuthenticationProvider headers: { "content-type": "application/x-www-form-urlencoded" }, body: data.toString() }); - const res = await auth.json(); + const res = (await auth.json()) as AUTH0_OAUTH_RESPONSE; console.log("🉑🉑🉑🉑 auth", res); return res; } @@ -299,7 +307,7 @@ class Auth0AuthenticationProvider } private async getPlatformUserInfo(token: string) { - const response = await fetch(`https://${PLATFORM_DOMAIN}/api/user-info`, { + const response = await fetch(`https://${SEQERA_API_URL}/user-info`, { headers: { Authorization: `Bearer ${token}` } diff --git a/src/constants.ts b/src/constants.ts index 5e9e9d6d..46ceaf4f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,5 @@ -export const SEQERA_PLATFORM_URL = `https://cloud.seqera.io`; +// export const SEQERA_PLATFORM_URL = `https://cloud.seqera.io`; +export const SEQERA_PLATFORM_URL = `https://pr-8246.dev-tower.net`; export const SEQERA_API_URL = `${SEQERA_PLATFORM_URL}/api`; export const SEQERA_HUB_API_URL = `https://hub.seqera.io`; export const SEQERA_INTERN_API_URL = `https://intern.seqera.io`; diff --git a/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts b/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts index 213b31e9..af96b40d 100644 --- a/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts +++ b/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts @@ -3,13 +3,14 @@ import type { UserInfoResponse } from "../types"; const fetchUserInfo = async (token: string): Promise => { if (!token) return { message: "No token found" } as UserInfoResponse; + console.log("🟢 fetchUserInfo", token); try { const response = await fetch(`${SEQERA_API_URL}/user-info`, { headers: { Authorization: `Bearer ${token}` } }); - console.log("🟣 fetchUserInfo", response.status); + console.log("🟣 fetchUserInfo", response); if (response.status === 401) { throw new Error("Unauthorized"); } From 3b77af7dc80bd6bd5a5371c8f0710722c186a017 Mon Sep 17 00:00:00 2001 From: Jake Broughton Date: Tue, 20 May 2025 17:18:00 +0200 Subject: [PATCH 03/11] Fixes --- src/auth/AuthProvider/index.ts | 51 +++++++++++-------- .../lib/platform/utils/fetchUserInfo.ts | 1 - 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/auth/AuthProvider/index.ts b/src/auth/AuthProvider/index.ts index efce62ab..d097ac91 100644 --- a/src/auth/AuthProvider/index.ts +++ b/src/auth/AuthProvider/index.ts @@ -16,6 +16,7 @@ import { v4 as uuid } from "uuid"; import { PromiseAdapter, promiseFromEvent } from "./utils/promiseFromEvent"; import fetch from "node-fetch"; import { SEQERA_API_URL } from "../../constants"; +import { UserInfo } from "./types"; export const AUTH_TYPE = `auth0`; const AUTH_NAME = `Auth0`; @@ -25,7 +26,7 @@ var CLIENT_SECRET = const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`; export const SESSIONS_SECRET_KEY = `${AUTH_TYPE}.sessions`; -type AUTH0_OAUTH_RESPONSE = { +type Auth0oAuthResponse = { access_token: string; refresh_token?: string; token_type: "Bearer"; @@ -34,6 +35,19 @@ type AUTH0_OAUTH_RESPONSE = { id_token: string; }; +type Auth0UserInfo = { + email: string; + email_verified: boolean; + family_name: string; + given_name: string; + name: string; + nickname: string; + picture: string; + preferred_username: string; + sub: string; + updated_at: string; +}; + class UriEventHandler extends EventEmitter implements UriHandler { public handleUri(uri: Uri) { this.fire(uri); @@ -97,7 +111,7 @@ class Auth0AuthenticationProvider if (!code) { throw new Error(`Auth0 login failure`); } - const auth0Response = await this.getToken(code); + const auth0Response = await this.fetchAuth0Tokens(code); console.log("🉑 auth0Response", auth0Response); token = auth0Response.access_token; } else { @@ -106,19 +120,16 @@ class Auth0AuthenticationProvider throw new Error(`Auth0 login failure`); } } - const userinfo = (await this.getUserInfo(token)) as any; - // const puserinfo = (await this.getPlatformUserInfo(token)) as { - // user: { id: string; userName: string; email: string }; - // }; - // const platformuser: { id: string; userName: string; email: string } = - // puserinfo.user; + const { email, name, nickname } = await this.fetchAuth0UserInfo(token); + const userInfo = await this.fetchPlatformUserInfo(token); + console.log("🉑 userInfo", userInfo); const session: AuthenticationSession = { id: uuid(), accessToken: token, account: { - label: userinfo.name + "(seqera: " + userinfo.nickname + " )", - id: userinfo.email + label: name + "(seqera: " + nickname + " )", + id: email }, scopes: [] }; @@ -276,7 +287,7 @@ class Auth0AuthenticationProvider resolve(accessToken); }; - private async getToken(code: string): Promise { + private async fetchAuth0Tokens(code: string): Promise { const data = new URLSearchParams([ ["grant_type", "authorization_code"], ["client_id", CLIENT_ID], @@ -289,31 +300,29 @@ class Auth0AuthenticationProvider headers: { "content-type": "application/x-www-form-urlencoded" }, body: data.toString() }); - const res = (await auth.json()) as AUTH0_OAUTH_RESPONSE; - console.log("🉑🉑🉑🉑 auth", res); + const res = (await auth.json()) as Auth0oAuthResponse; return res; } - private async getUserInfo(token: string) { - console.log("🉑 getUserInfo", token); + private async fetchAuth0UserInfo(token: string): Promise { const response = await fetch(`https://${AUTH0_DOMAIN}/userinfo`, { headers: { Authorization: `Bearer ${token}` } }); - const res = await response.json(); - console.log("🉑 getUserInfo", res); + const res = (await response.json()) as Auth0UserInfo; return res; } - private async getPlatformUserInfo(token: string) { - const response = await fetch(`https://${SEQERA_API_URL}/user-info`, { + private async fetchPlatformUserInfo(token: string): Promise { + const response = await fetch(`${SEQERA_API_URL}/user-info`, { headers: { Authorization: `Bearer ${token}` } }); - console.log("🉑 getPlatformUserInfo", response); - return await response.json(); + const res = (await response.json()) as UserInfo; + console.log("🉑 res", res); + return res; } public setWebview(webview: any) { diff --git a/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts b/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts index af96b40d..f074c6c7 100644 --- a/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts +++ b/src/webview/WebviewProvider/lib/platform/utils/fetchUserInfo.ts @@ -3,7 +3,6 @@ import type { UserInfoResponse } from "../types"; const fetchUserInfo = async (token: string): Promise => { if (!token) return { message: "No token found" } as UserInfoResponse; - console.log("🟢 fetchUserInfo", token); try { const response = await fetch(`${SEQERA_API_URL}/user-info`, { headers: { From ba999ca83818c89feb41c5a78f88f3a9bfe9994a Mon Sep 17 00:00:00 2001 From: Jake Broughton Date: Tue, 20 May 2025 19:08:05 +0200 Subject: [PATCH 04/11] Fixes/refactor --- src/auth/AuthProvider/index.ts | 56 +++++++++------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/src/auth/AuthProvider/index.ts b/src/auth/AuthProvider/index.ts index d097ac91..b79c8cd1 100644 --- a/src/auth/AuthProvider/index.ts +++ b/src/auth/AuthProvider/index.ts @@ -9,14 +9,13 @@ import { ExtensionContext, ProgressLocation, Uri, - UriHandler, window } from "vscode"; import { v4 as uuid } from "uuid"; import { PromiseAdapter, promiseFromEvent } from "./utils/promiseFromEvent"; import fetch from "node-fetch"; -import { SEQERA_API_URL } from "../../constants"; -import { UserInfo } from "./types"; +import { fetchUserInfo } from "../../webview/WebviewProvider/lib/platform/utils"; +import UriEventHandler from "./utils/UriEventHandler"; export const AUTH_TYPE = `auth0`; const AUTH_NAME = `Auth0`; @@ -26,7 +25,7 @@ var CLIENT_SECRET = const AUTH0_DOMAIN = `seqera-development.eu.auth0.com`; export const SESSIONS_SECRET_KEY = `${AUTH_TYPE}.sessions`; -type Auth0oAuthResponse = { +type ResponseAuth0 = { access_token: string; refresh_token?: string; token_type: "Bearer"; @@ -35,7 +34,7 @@ type Auth0oAuthResponse = { id_token: string; }; -type Auth0UserInfo = { +type UserInfoAuth0 = { email: string; email_verified: boolean; family_name: string; @@ -48,12 +47,6 @@ type Auth0UserInfo = { updated_at: string; }; -class UriEventHandler extends EventEmitter implements UriHandler { - public handleUri(uri: Uri) { - this.fire(uri); - } -} - class Auth0AuthenticationProvider implements AuthenticationProvider, Disposable { @@ -66,7 +59,8 @@ class Auth0AuthenticationProvider { promise: Promise; cancel: EventEmitter } >(); private _uriHandler = new UriEventHandler(); - private webviewView: any; + private webviewView: string | undefined; + constructor(private readonly context: ExtensionContext) { this._disposable = Disposable.from( authentication.registerAuthenticationProvider( @@ -108,20 +102,16 @@ class Auth0AuthenticationProvider // Note: for getting a refresh token, we need to use this "code" flow. // Use the Auth0 app's secret, and ensure "Allow Offline Access" is enabled. const code = await this.startLogin(scopes, "code"); - if (!code) { - throw new Error(`Auth0 login failure`); - } + console.log("🟢 code", code); + if (!code) throw new Error(`Auth0 login failure (code flow)`); const auth0Response = await this.fetchAuth0Tokens(code); - console.log("🉑 auth0Response", auth0Response); token = auth0Response.access_token; } else { token = await this.startLogin(scopes, "token"); - if (!token) { - throw new Error(`Auth0 login failure`); - } + if (!token) throw new Error(`Auth0 login failure (token flow)`); } - const { email, name, nickname } = await this.fetchAuth0UserInfo(token); - const userInfo = await this.fetchPlatformUserInfo(token); + const { email, name, nickname } = await this.fetchUserInfoAuth0(token); + const userInfo = await fetchUserInfo(token); console.log("🉑 userInfo", userInfo); const session: AuthenticationSession = { @@ -264,11 +254,6 @@ class Auth0AuthenticationProvider : new URLSearchParams(uri.fragment); const accessToken = query.get("code") || query.get("access_token"); const state = query.get("state"); - console.log("🟢 handleUri uri", uri); - console.log("🟢 handleUri query", query); - console.log("🟢 handleUri code", query.get("code")); - console.log("🟢 handleUri access_token", query.get("access_token")); - console.log("🟢 handleUri state", query.get("state")); if (!accessToken) { reject(new Error("No token")); @@ -287,7 +272,7 @@ class Auth0AuthenticationProvider resolve(accessToken); }; - private async fetchAuth0Tokens(code: string): Promise { + private async fetchAuth0Tokens(code: string): Promise { const data = new URLSearchParams([ ["grant_type", "authorization_code"], ["client_id", CLIENT_ID], @@ -300,28 +285,17 @@ class Auth0AuthenticationProvider headers: { "content-type": "application/x-www-form-urlencoded" }, body: data.toString() }); - const res = (await auth.json()) as Auth0oAuthResponse; + const res = (await auth.json()) as ResponseAuth0; return res; } - private async fetchAuth0UserInfo(token: string): Promise { + private async fetchUserInfoAuth0(token: string): Promise { const response = await fetch(`https://${AUTH0_DOMAIN}/userinfo`, { headers: { Authorization: `Bearer ${token}` } }); - const res = (await response.json()) as Auth0UserInfo; - return res; - } - - private async fetchPlatformUserInfo(token: string): Promise { - const response = await fetch(`${SEQERA_API_URL}/user-info`, { - headers: { - Authorization: `Bearer ${token}` - } - }); - const res = (await response.json()) as UserInfo; - console.log("🉑 res", res); + const res = (await response.json()) as UserInfoAuth0; return res; } From 57a58c3a82df14a486d5d74f38ab7e47623e941a Mon Sep 17 00:00:00 2001 From: Jake Broughton Date: Tue, 20 May 2025 19:12:56 +0200 Subject: [PATCH 05/11] UI update --- .../src/Layout/SeqeraCloud/Toolbar/WorkspaceSelector.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/Layout/SeqeraCloud/Toolbar/WorkspaceSelector.tsx b/webview-ui/src/Layout/SeqeraCloud/Toolbar/WorkspaceSelector.tsx index 1f0d2dba..915c07ca 100644 --- a/webview-ui/src/Layout/SeqeraCloud/Toolbar/WorkspaceSelector.tsx +++ b/webview-ui/src/Layout/SeqeraCloud/Toolbar/WorkspaceSelector.tsx @@ -2,6 +2,7 @@ import { useTowerContext } from "../../../Context"; import Select from "../../../components/Select"; import { getWorkspaceURL } from "../utils"; import Button from "../../../components/Button"; +import { SEQERA_PLATFORM_URL } from "../../../../../src/constants"; const WorkspaceSelector = () => { const { @@ -36,7 +37,9 @@ const WorkspaceSelector = () => { subtle /> ) : ( -
No workspaces found
+ )} {!!manageURL && (