From 37ec7b4d60c8b1d2430176029de198fcc438f33d Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 19 Jun 2024 13:22:01 -0700 Subject: [PATCH 01/13] added token refresh when expired and moved code challenge to server --- fission/src/aps/APS.ts | 102 ++++++++++++++++++------------- fission/src/test/aps/APS.test.ts | 40 ------------ 2 files changed, 59 insertions(+), 83 deletions(-) delete mode 100644 fission/src/test/aps/APS.test.ts diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 233fc2099a..57e590494e 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -10,7 +10,6 @@ const delay = 1000 const authCodeTimeout = 200000 const CLIENT_ID = 'GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU' -const CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' let lastCall = Date.now() @@ -18,6 +17,7 @@ interface APSAuth { access_token: string; refresh_token: string; expires_in: number; + expires_at: number; token_type: number; } @@ -34,7 +34,7 @@ class APS { static get auth(): APSAuth | undefined { const res = window.localStorage.getItem(APS_AUTH_KEY) try { - return res ? JSON.parse(res) as APSAuth : undefined + return res ? JSON.parse(res) : undefined; } catch (e) { console.warn(`Failed to parse stored APS auth data: ${e}`) return undefined @@ -49,6 +49,16 @@ class APS { this.userInfo = undefined } + static async getAuth(): Promise { + const auth = this.auth; + if (!auth) return new Promise((resolve) => resolve(undefined)); + + if (Date.now() > auth.expires_at) { + await this.refreshAuthToken(auth.refresh_token); + } + return new Promise((resolve) => resolve(this.auth)); + } + static get userInfo(): APSUserInfo | undefined { const res = window.localStorage.getItem(APS_USER_INFO_KEY) @@ -80,22 +90,21 @@ class APS { ? `http://localhost:3000${import.meta.env.BASE_URL}` : `https://synthesis.autodesk.com${import.meta.env.BASE_URL}` - const [ codeVerifier, codeChallenge ] = await this.codeChallenge(); - - const dataParams = [ - ['response_type', 'code'], - ['client_id', CLIENT_ID], - ['redirect_uri', callbackUrl], - ['scope', 'data:read'], - ['nonce', Date.now().toString()], - ['prompt', 'login'], - ['code_challenge', codeChallenge], - ['code_challenge_method', 'S256'] - ] - const data = dataParams.map(x => `${x[0]}=${encodeURIComponent(x[1])}`).join('&') - - window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${data}`) - + const challenge = await this.codeChallenge(); + + const params = new URLSearchParams({ + 'response_type': 'code', + 'client_id': CLIENT_ID, + 'redirect_uri': callbackUrl, + 'scope': 'data:read', + 'nonce': Date.now().toString(), + 'prompt': 'login', + 'code_challenge': challenge, + 'code_challenge_method': 'S256' + }) + + window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`) + const searchStart = Date.now() const func = () => { if (Date.now() - searchStart > authCodeTimeout) { @@ -107,7 +116,7 @@ class APS { const code = this.authCode; this.authCode = undefined; - this.convertAuthToken(code, codeVerifier) + this.convertAuthToken(code) } else { setTimeout(func, 500) } @@ -116,10 +125,33 @@ class APS { } } - static async convertAuthToken(code: string, codeVerifier: string) { + static async refreshAuthToken(refresh_token: string) { + let now + if ((now = Date.now()) - lastCall > delay) { + lastCall = now + const res = await fetch('https://developer.api.autodesk.com/authentication/v2/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + 'client_id': CLIENT_ID, + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token, + 'scope': 'data:read', + }) + }).then(res => res.json()); + res.expires_at = res.expires_in + Date.now() + this.auth = res as APSAuth + } + } + + static async convertAuthToken(code: string) { const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` : `https://synthesis.autodesk.com/api/aps/code/` - fetch(`${authUrl}?code=${code}&code_verifier=${codeVerifier}`).then(x => x.json()).then(x => { - this.auth = x.response as APSAuth; + fetch(`${authUrl}?code=${code}`).then(x => x.json()).then(x => { + const auth = x.response as APSAuth; + auth.expires_at = auth.expires_in + Date.now() + this.auth = auth }).then(() => { console.log('Preloading user info') if (this.auth) { @@ -152,26 +184,10 @@ class APS { } static async codeChallenge() { - const codeVerifier = this.genRandomString(50) - - const msgBuffer = new TextEncoder().encode(codeVerifier); - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); - - let str = ''; - (new Uint8Array(hashBuffer)).forEach(x => str = str + String.fromCharCode(x)) - const codeChallenge = btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') - - return [ codeVerifier, codeChallenge ] - } - - static genRandomString(len: number): string { - const s: string[] = [] - for (let i = 0; i < len; i++) { - const c = CHARACTERS.charAt(Math.abs(Random() * 10000) % CHARACTERS.length) - s.push(c) - } - - return s.join('') + const endpoint = import.meta.env.DEV ? 'http://localhost:3003/api/aps/challenge/' : 'https://synthesis.autodesk.com/api/aps/challenge/'; + const res = await fetch(endpoint) + const json = await res.json() + return json['challenge'] } } @@ -179,4 +195,4 @@ Window.prototype.setAuthCode = (code: string) => { APS.authCode = code } -export default APS \ No newline at end of file +export default APS diff --git a/fission/src/test/aps/APS.test.ts b/fission/src/test/aps/APS.test.ts deleted file mode 100644 index 3e7da5f219..0000000000 --- a/fission/src/test/aps/APS.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import APS from "@/aps/APS"; -import { describe, expect, test } from "vitest"; - -describe('APS', () => { - test('Generate Random Strings (10)', () => { - const s = APS.genRandomString(10); - - (async () => { - const [v, c] = await APS.codeChallenge() - console.log(`${v}`) - console.log(`${c}`) - })() - - expect(s.length).toBe(10) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) - - test('Generate Random Strings (50)', () => { - const s = APS.genRandomString(50) - - expect(s.length).toBe(50) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) - - test('Generate Random Strings (75)', () => { - const s = APS.genRandomString(75) - - expect(s.length).toBe(75) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) -}) \ No newline at end of file From 0a235d72d57caced4b2e7a531731f88231eeeb75 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 19 Jun 2024 14:20:40 -0700 Subject: [PATCH 02/13] use correct getter to actually refresh token when necessary --- fission/src/aps/APS.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 57e590494e..7d6aad5e24 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -152,10 +152,11 @@ class APS { const auth = x.response as APSAuth; auth.expires_at = auth.expires_in + Date.now() this.auth = auth - }).then(() => { + }).then(async () => { console.log('Preloading user info') - if (this.auth) { - this.loadUserInfo(this.auth!).then(async () => { + const auth = await this.getAuth(); + if (auth) { + this.loadUserInfo(auth).then(async () => { if (APS.userInfo) { MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) } From c70324e0e85bbf7953d1e86c4dfad24fb1c6b060 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Thu, 27 Jun 2024 15:57:34 -0700 Subject: [PATCH 03/13] add .aps_auth to .gitignore --- exporter/SynthesisFusionAddin/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/.gitignore b/exporter/SynthesisFusionAddin/.gitignore index eca33a87a8..e7ae5df513 100644 --- a/exporter/SynthesisFusionAddin/.gitignore +++ b/exporter/SynthesisFusionAddin/.gitignore @@ -107,4 +107,5 @@ site-packages # env files **/.env -proto/proto_out \ No newline at end of file +proto/proto_out +.aps_auth From 38e601f85cb9e583cfc4407e3f64178f0219700d Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 19 Jun 2024 13:22:01 -0700 Subject: [PATCH 04/13] added token refresh when expired and moved code challenge to server --- fission/src/aps/APS.ts | 133 ++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 009f4a4901..5f3e6932b8 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -8,16 +8,16 @@ export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" const delay = 1000 const authCodeTimeout = 200000 -const CLIENT_ID = "GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU" -const CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +const CLIENT_ID = 'GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU' let lastCall = Date.now() interface APSAuth { - access_token: string - refresh_token: string - expires_in: number - token_type: number + access_token: string; + refresh_token: string; + expires_in: number; + expires_at: number; + token_type: number; } interface APSUserInfo { @@ -32,7 +32,7 @@ class APS { static get auth(): APSAuth | undefined { const res = window.localStorage.getItem(APS_AUTH_KEY) try { - return res ? (JSON.parse(res) as APSAuth) : undefined + return res ? JSON.parse(res) : undefined; } catch (e) { console.warn(`Failed to parse stored APS auth data: ${e}`) return undefined @@ -47,6 +47,16 @@ class APS { this.userInfo = undefined } + static async getAuth(): Promise { + const auth = this.auth; + if (!auth) return new Promise((resolve) => resolve(undefined)); + + if (Date.now() > auth.expires_at) { + await this.refreshAuthToken(auth.refresh_token); + } + return new Promise((resolve) => resolve(this.auth)); + } + static get userInfo(): APSUserInfo | undefined { const res = window.localStorage.getItem(APS_USER_INFO_KEY) @@ -78,21 +88,20 @@ class APS { ? `http://localhost:3000${import.meta.env.BASE_URL}` : `https://synthesis.autodesk.com${import.meta.env.BASE_URL}` - const [codeVerifier, codeChallenge] = await this.codeChallenge() - - const dataParams = [ - ["response_type", "code"], - ["client_id", CLIENT_ID], - ["redirect_uri", callbackUrl], - ["scope", "data:read"], - ["nonce", Date.now().toString()], - ["prompt", "login"], - ["code_challenge", codeChallenge], - ["code_challenge_method", "S256"], - ] - const data = dataParams.map(x => `${x[0]}=${encodeURIComponent(x[1])}`).join("&") + const challenge = await this.codeChallenge(); + + const params = new URLSearchParams({ + 'response_type': 'code', + 'client_id': CLIENT_ID, + 'redirect_uri': callbackUrl, + 'scope': 'data:read', + 'nonce': Date.now().toString(), + 'prompt': 'login', + 'code_challenge': challenge, + 'code_challenge_method': 'S256' + }) - window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${data}`) + window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`) const searchStart = Date.now() const func = () => { @@ -105,7 +114,7 @@ class APS { const code = this.authCode this.authCode = undefined - this.convertAuthToken(code, codeVerifier) + this.convertAuthToken(code) } else { setTimeout(func, 500) } @@ -114,25 +123,43 @@ class APS { } } - static async convertAuthToken(code: string, codeVerifier: string) { - const authUrl = import.meta.env.DEV - ? `http://localhost:3003/api/aps/code/` - : `https://synthesis.autodesk.com/api/aps/code/` - fetch(`${authUrl}?code=${code}&code_verifier=${codeVerifier}`) - .then(x => x.json()) - .then(x => { - this.auth = x.response as APSAuth - }) - .then(() => { - console.log("Preloading user info") - if (this.auth) { - this.loadUserInfo(this.auth!).then(async () => { - if (APS.userInfo) { - MainHUD_AddToast("info", "ADSK Login", `Hello, ${APS.userInfo.givenName}`) - } - }) - } - }) + static async refreshAuthToken(refresh_token: string) { + let now + if ((now = Date.now()) - lastCall > delay) { + lastCall = now + const res = await fetch('https://developer.api.autodesk.com/authentication/v2/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + 'client_id': CLIENT_ID, + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token, + 'scope': 'data:read', + }) + }).then(res => res.json()); + res.expires_at = res.expires_in + Date.now() + this.auth = res as APSAuth + } + } + + static async convertAuthToken(code: string) { + const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` : `https://synthesis.autodesk.com/api/aps/code/` + fetch(`${authUrl}?code=${code}`).then(x => x.json()).then(x => { + const auth = x.response as APSAuth; + auth.expires_at = auth.expires_in + Date.now() + this.auth = auth + }).then(() => { + console.log('Preloading user info') + if (this.auth) { + this.loadUserInfo(this.auth!).then(async () => { + if (APS.userInfo) { + MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) + } + }) + } + }) } static async loadUserInfo(auth: APSAuth) { @@ -156,26 +183,10 @@ class APS { } static async codeChallenge() { - const codeVerifier = this.genRandomString(50) - - const msgBuffer = new TextEncoder().encode(codeVerifier) - const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer) - - let str = "" - new Uint8Array(hashBuffer).forEach(x => (str = str + String.fromCharCode(x))) - const codeChallenge = btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") - - return [codeVerifier, codeChallenge] - } - - static genRandomString(len: number): string { - const s: string[] = [] - for (let i = 0; i < len; i++) { - const c = CHARACTERS.charAt(Math.abs(Random() * 10000) % CHARACTERS.length) - s.push(c) - } - - return s.join("") + const endpoint = import.meta.env.DEV ? 'http://localhost:3003/api/aps/challenge/' : 'https://synthesis.autodesk.com/api/aps/challenge/'; + const res = await fetch(endpoint) + const json = await res.json() + return json['challenge'] } } From f4386f6c3a8ee0893dd7d02208eebbf3ef4a09f2 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 19 Jun 2024 14:20:40 -0700 Subject: [PATCH 05/13] use correct getter to actually refresh token when necessary --- fission/src/aps/APS.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 5f3e6932b8..194504577a 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -150,10 +150,11 @@ class APS { const auth = x.response as APSAuth; auth.expires_at = auth.expires_in + Date.now() this.auth = auth - }).then(() => { + }).then(async () => { console.log('Preloading user info') - if (this.auth) { - this.loadUserInfo(this.auth!).then(async () => { + const auth = await this.getAuth(); + if (auth) { + this.loadUserInfo(auth).then(async () => { if (APS.userInfo) { MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) } From cae67bda2272e727b97c7ad79ce83eddfcd21f80 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Thu, 27 Jun 2024 15:57:34 -0700 Subject: [PATCH 06/13] add .aps_auth to .gitignore --- exporter/SynthesisFusionAddin/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/.gitignore b/exporter/SynthesisFusionAddin/.gitignore index eca33a87a8..e7ae5df513 100644 --- a/exporter/SynthesisFusionAddin/.gitignore +++ b/exporter/SynthesisFusionAddin/.gitignore @@ -107,4 +107,5 @@ site-packages # env files **/.env -proto/proto_out \ No newline at end of file +proto/proto_out +.aps_auth From 9585fb9f1c180c9d4a57ff00509c11560b6bdad5 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Fri, 28 Jun 2024 17:15:19 -0700 Subject: [PATCH 07/13] added error handling for APS authorization --- fission/src/Synthesis.tsx | 7 +- fission/src/aps/APS.ts | 97 ++++++++++++++------------- fission/src/ui/components/MainHUD.tsx | 16 +++-- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index daefd9f9be..a34610cd67 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -54,6 +54,7 @@ import World from '@/systems/World.ts'; import { AddRobotsModal, AddFieldsModal, SpawningModal } from '@/modals/spawning/SpawningModals.tsx'; import ImportMirabufModal from '@/modals/mirabuf/ImportMirabufModal.tsx'; import ImportLocalMirabufModal from '@/modals/mirabuf/ImportLocalMirabufModal.tsx'; +import APS from "./aps/APS.ts" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -61,8 +62,10 @@ function Synthesis() { const urlParams = new URLSearchParams(document.location.search) if (urlParams.has("code")) { const code = urlParams.get("code") - window.opener?.setAuthCode(code) - window.close() + if (code) { + APS.convertAuthToken(code); + window.close() + } } const { openModal, closeModal, getActiveModalElement } = useModalManager(initialModals) diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 194504577a..b17eeff0d1 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -1,17 +1,16 @@ +import { MainHUD_AddToast } from "@/ui/components/MainHUD" import { Random } from "@/util/Random" +import { Mutex } from "async-mutex" const APS_AUTH_KEY = "aps_auth" const APS_USER_INFO_KEY = "aps_user_info" export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" -const delay = 1000 const authCodeTimeout = 200000 const CLIENT_ID = 'GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU' -let lastCall = Date.now() - interface APSAuth { access_token: string; refresh_token: string; @@ -28,6 +27,7 @@ interface APSUserInfo { class APS { static authCode: string | undefined = undefined + static requestMutex: Mutex = new Mutex() static get auth(): APSAuth | undefined { const res = window.localStorage.getItem(APS_AUTH_KEY) @@ -39,7 +39,7 @@ class APS { } } - static set auth(a: APSAuth | undefined) { + private static set auth(a: APSAuth | undefined) { window.localStorage.removeItem(APS_AUTH_KEY) if (a) { window.localStorage.setItem(APS_AUTH_KEY, JSON.stringify(a)) @@ -49,12 +49,12 @@ class APS { static async getAuth(): Promise { const auth = this.auth; - if (!auth) return new Promise((resolve) => resolve(undefined)); + if (!auth) return undefined; if (Date.now() > auth.expires_at) { await this.refreshAuthToken(auth.refresh_token); } - return new Promise((resolve) => resolve(this.auth)); + return this.auth; } static get userInfo(): APSUserInfo | undefined { @@ -82,51 +82,36 @@ class APS { } static async requestAuthCode() { - if (Date.now() - lastCall > delay) { - lastCall = Date.now() + await this.requestMutex.runExclusive(async () => { const callbackUrl = import.meta.env.DEV ? `http://localhost:3000${import.meta.env.BASE_URL}` : `https://synthesis.autodesk.com${import.meta.env.BASE_URL}` - const challenge = await this.codeChallenge(); - - const params = new URLSearchParams({ - 'response_type': 'code', - 'client_id': CLIENT_ID, - 'redirect_uri': callbackUrl, - 'scope': 'data:read', - 'nonce': Date.now().toString(), - 'prompt': 'login', - 'code_challenge': challenge, - 'code_challenge_method': 'S256' - }) - - window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`) - - const searchStart = Date.now() - const func = () => { - if (Date.now() - searchStart > authCodeTimeout) { - console.debug("Auth Code Timeout") - return - } + try { + const challenge = await this.codeChallenge(); - if (this.authCode) { - const code = this.authCode - this.authCode = undefined + const params = new URLSearchParams({ + 'response_type': 'code', + 'client_id': CLIENT_ID, + 'redirect_uri': callbackUrl, + 'scope': 'data:read', + 'nonce': Date.now().toString(), + 'prompt': 'login', + 'code_challenge': challenge, + 'code_challenge_method': 'S256' + }) - this.convertAuthToken(code) - } else { - setTimeout(func, 500) - } + window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`) + } catch (e) { + console.error(e); + MainHUD_AddToast("error", "Error signing in.", "Please try again."); } - func() - } + }) } static async refreshAuthToken(refresh_token: string) { - let now - if ((now = Date.now()) - lastCall > delay) { - lastCall = now + await this.requestMutex.runExclusive(async () => { + let retry_login = false; const res = await fetch('https://developer.api.autodesk.com/authentication/v2/token', { method: 'POST', headers: { @@ -138,14 +123,26 @@ class APS { 'refresh_token': refresh_token, 'scope': 'data:read', }) - }).then(res => res.json()); - res.expires_at = res.expires_in + Date.now() - this.auth = res as APSAuth - } + }) + .then(res => res.json()) + .catch(e => { + MainHUD_AddToast("error", "Error signing in.", "Please try again."); + console.error(e); + retry_login = true; + }); + if (retry_login) { + this.auth = undefined; + this.requestAuthCode(); + } else { + res.expires_at = res.expires_in + Date.now() + this.auth = res as APSAuth + } + }) } static async convertAuthToken(code: string) { const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` : `https://synthesis.autodesk.com/api/aps/code/` + let retry_login = false; fetch(`${authUrl}?code=${code}`).then(x => x.json()).then(x => { const auth = x.response as APSAuth; auth.expires_at = auth.expires_in + Date.now() @@ -159,8 +156,18 @@ class APS { MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) } }) + } else { + console.error("Couldn't get auth data."); + retry_login = true; } + }).catch(e => { + console.error(e); + retry_login = true; }) + if (retry_login) { + this.auth = undefined; + MainHUD_AddToast('error', 'Error signing in.', 'Please try again.'); + } } static async loadUserInfo(auth: APSAuth) { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 64b9e88c0f..0fcb9c92ed 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -5,7 +5,7 @@ import { BiMenuAltLeft } from "react-icons/bi" import { GrFormClose } from "react-icons/gr" import { GiSteeringWheel } from "react-icons/gi" import { HiDownload } from "react-icons/hi" -import { IoGameControllerOutline, IoPeople } from "react-icons/io5" +import { IoGameControllerOutline, IoPeople, IoRefresh } from "react-icons/io5" import { useModalControlContext } from "@/ui/ModalContext" import { usePanelControlContext } from "@/ui/PanelContext" import { motion } from "framer-motion" @@ -36,7 +36,7 @@ const MainHUDButton: React.FC = ({ value, icon, onClick, larger }) > {larger && icon} {!larger && ( - + {icon} )} @@ -130,6 +130,11 @@ const MainHUD: React.FC = () => { icon={} onClick={TestGodMode} /> + } + onClick={() => APS.auth && APS.refreshAuthToken(APS.auth.refresh_token)} + />
{ }} />
- {userInfo ? ( + {userInfo + ? } larger={true} onClick={() => APS.logout()} /> - ) : ( + : } larger={true} onClick={() => APS.requestAuthCode()} /> - )} + } ) From d4951604a9c8c5c5dd94767f14d6495c8e30e2e0 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Fri, 28 Jun 2024 17:44:11 -0700 Subject: [PATCH 08/13] fixed sign-in and added expire token main hud button --- fission/src/Synthesis.tsx | 2 +- fission/src/aps/APS.ts | 22 ++++++++++++---------- fission/src/ui/components/MainHUD.tsx | 12 ++++++++++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index a34610cd67..357e61a5e4 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -64,7 +64,7 @@ function Synthesis() { const code = urlParams.get("code") if (code) { APS.convertAuthToken(code); - window.close() + document.location.search = ''; } } diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index b17eeff0d1..8aa66ca646 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -1,5 +1,4 @@ import { MainHUD_AddToast } from "@/ui/components/MainHUD" -import { Random } from "@/util/Random" import { Mutex } from "async-mutex" const APS_AUTH_KEY = "aps_auth" @@ -7,8 +6,6 @@ const APS_USER_INFO_KEY = "aps_user_info" export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" -const authCodeTimeout = 200000 - const CLIENT_ID = 'GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU' interface APSAuth { @@ -101,7 +98,7 @@ class APS { 'code_challenge_method': 'S256' }) - window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`) + window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`, '_self') } catch (e) { console.error(e); MainHUD_AddToast("error", "Error signing in.", "Please try again."); @@ -126,16 +123,22 @@ class APS { }) .then(res => res.json()) .catch(e => { - MainHUD_AddToast("error", "Error signing in.", "Please try again."); console.error(e); retry_login = true; }); if (retry_login) { + MainHUD_AddToast("error", "Error signing in.", "Please try again."); this.auth = undefined; this.requestAuthCode(); } else { res.expires_at = res.expires_in + Date.now() this.auth = res as APSAuth + if (this.auth) { + await this.loadUserInfo(this.auth); + if (APS.userInfo) { + MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`); + } + } } }) } @@ -151,11 +154,10 @@ class APS { console.log('Preloading user info') const auth = await this.getAuth(); if (auth) { - this.loadUserInfo(auth).then(async () => { - if (APS.userInfo) { - MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) - } - }) + await this.loadUserInfo(auth); + if (APS.userInfo) { + MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) + } } else { console.error("Couldn't get auth data."); retry_login = true; diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 0fcb9c92ed..c2275266e9 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -5,7 +5,7 @@ import { BiMenuAltLeft } from "react-icons/bi" import { GrFormClose } from "react-icons/gr" import { GiSteeringWheel } from "react-icons/gi" import { HiDownload } from "react-icons/hi" -import { IoGameControllerOutline, IoPeople, IoRefresh } from "react-icons/io5" +import { IoGameControllerOutline, IoPeople, IoRefresh, IoTimer } from "react-icons/io5" import { useModalControlContext } from "@/ui/ModalContext" import { usePanelControlContext } from "@/ui/PanelContext" import { motion } from "framer-motion" @@ -45,7 +45,7 @@ const MainHUDButton: React.FC = ({ value, icon, onClick, larger }) ) } -export let MainHUD_AddToast: (type: ToastType, title: string, description: string) => void = (_a, _b, _c) => {} +export let MainHUD_AddToast: (type: ToastType, title: string, description: string) => void = (_a, _b, _c) => { } const variants = { open: { opacity: 1, y: "-50%", x: 0 }, @@ -135,6 +135,14 @@ const MainHUD: React.FC = () => { icon={} onClick={() => APS.auth && APS.refreshAuthToken(APS.auth.refresh_token)} /> + } + onClick={() => { + if (APS.auth) APS.auth.expires_at = Date.now() + APS.getAuth() + }} + />
Date: Sat, 29 Jun 2024 15:25:26 -0700 Subject: [PATCH 09/13] fix authentication and prefill email on login if it's still around --- fission/src/Synthesis.tsx | 14 +++++++------- fission/src/aps/APS.ts | 29 +++++++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 357e61a5e4..187d60c266 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -60,14 +60,15 @@ const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" function Synthesis() { const urlParams = new URLSearchParams(document.location.search) - if (urlParams.has("code")) { + const has_code = urlParams.has("code") + if (has_code) { const code = urlParams.get("code") if (code) { - APS.convertAuthToken(code); - document.location.search = ''; + APS.convertAuthToken(code).then(() => { + document.location.search = '' + }); } } - const { openModal, closeModal, getActiveModalElement } = useModalManager(initialModals) const { openPanel, closePanel, closeAllPanels, getActivePanelElements } = usePanelManager(initialPanels) const { showTooltip } = useTooltipManager() @@ -82,17 +83,16 @@ function Synthesis() { const modalElement = getActiveModalElement() useEffect(() => { + if (has_code) return; + World.InitWorld() let mira_path = DEFAULT_MIRA_PATH - const urlParams = new URLSearchParams(document.location.search) - if (urlParams.has("mira")) { mira_path = `test_mira/${urlParams.get("mira")!}` console.debug(`Selected Mirabuf File: ${mira_path}`) } - console.log(urlParams) const setup = async () => { const miraAssembly = await LoadMirabufRemote(mira_path) diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 8aa66ca646..0d7d30cde0 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -20,13 +20,14 @@ interface APSUserInfo { name: string picture: string givenName: string + email: string } class APS { static authCode: string | undefined = undefined static requestMutex: Mutex = new Mutex() - static get auth(): APSAuth | undefined { + private static get auth(): APSAuth | undefined { const res = window.localStorage.getItem(APS_AUTH_KEY) try { return res ? JSON.parse(res) : undefined; @@ -98,7 +99,13 @@ class APS { 'code_challenge_method': 'S256' }) - window.open(`https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`, '_self') + if (APS.userInfo) { + params.append('authoptions', encodeURIComponent(JSON.stringify({ id: APS.userInfo.email }))); + } + + const url = `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`; + + window.open(url, '_self') } catch (e) { console.error(e); MainHUD_AddToast("error", "Error signing in.", "Please try again."); @@ -146,11 +153,12 @@ class APS { static async convertAuthToken(code: string) { const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` : `https://synthesis.autodesk.com/api/aps/code/` let retry_login = false; - fetch(`${authUrl}?code=${code}`).then(x => x.json()).then(x => { - const auth = x.response as APSAuth; - auth.expires_at = auth.expires_in + Date.now() - this.auth = auth - }).then(async () => { + try { + const res = await fetch(`${authUrl}?code=${code}`); + const json = await res.json() + const auth_res = json.response as APSAuth; + auth_res.expires_at = auth_res.expires_in + Date.now() + this.auth = auth_res console.log('Preloading user info') const auth = await this.getAuth(); if (auth) { @@ -162,10 +170,10 @@ class APS { console.error("Couldn't get auth data."); retry_login = true; } - }).catch(e => { + } catch (e) { console.error(e); - retry_login = true; - }) + retry_login = true + } if (retry_login) { this.auth = undefined; MainHUD_AddToast('error', 'Error signing in.', 'Please try again.'); @@ -186,6 +194,7 @@ class APS { name: x.name, givenName: x.given_name, picture: x.picture, + email: x.email } this.userInfo = info From ff44729e6080677233d200ce8b51477c213c03a5 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Sat, 29 Jun 2024 15:32:02 -0700 Subject: [PATCH 10/13] deleted old APS tests --- fission/src/test/aps/APS.test.ts | 40 -------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 fission/src/test/aps/APS.test.ts diff --git a/fission/src/test/aps/APS.test.ts b/fission/src/test/aps/APS.test.ts deleted file mode 100644 index 2721a33633..0000000000 --- a/fission/src/test/aps/APS.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import APS from "@/aps/APS" -import { describe, expect, test } from "vitest" - -describe("APS", () => { - test("Generate Random Strings (10)", () => { - const s = APS.genRandomString(10) - - ;(async () => { - const [v, c] = await APS.codeChallenge() - console.log(`${v}`) - console.log(`${c}`) - })() - - expect(s.length).toBe(10) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) - - test("Generate Random Strings (50)", () => { - const s = APS.genRandomString(50) - - expect(s.length).toBe(50) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) - - test("Generate Random Strings (75)", () => { - const s = APS.genRandomString(75) - - expect(s.length).toBe(75) - const matches = s.match(/^([0-9a-z])*$/i) - expect(matches).toBeDefined() - expect(matches!.length).toBeGreaterThanOrEqual(1) - expect(matches![0]).toBe(s) - }) -}) From bdc4964b2314e5e94bbaf168155a61d42696a746 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Sat, 29 Jun 2024 15:46:55 -0700 Subject: [PATCH 11/13] add Autodesk API errors and format --- fission/src/Synthesis.tsx | 16 +-- fission/src/aps/APS.ts | 193 +++++++++++++++----------- fission/src/ui/components/MainHUD.tsx | 21 +-- 3 files changed, 123 insertions(+), 107 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 187d60c266..3652d7b99b 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -49,11 +49,11 @@ import ScoringZonesPanel from "@/panels/configuring/scoring/ScoringZonesPanel" import ZoneConfigPanel from "@/panels/configuring/scoring/ZoneConfigPanel" import ScoreboardPanel from "@/panels/information/ScoreboardPanel" import DriverStationPanel from "@/panels/simulation/DriverStationPanel" -import ManageAssembliesModal from '@/modals/spawning/ManageAssembliesModal.tsx'; -import World from '@/systems/World.ts'; -import { AddRobotsModal, AddFieldsModal, SpawningModal } from '@/modals/spawning/SpawningModals.tsx'; -import ImportMirabufModal from '@/modals/mirabuf/ImportMirabufModal.tsx'; -import ImportLocalMirabufModal from '@/modals/mirabuf/ImportLocalMirabufModal.tsx'; +import ManageAssembliesModal from "@/modals/spawning/ManageAssembliesModal.tsx" +import World from "@/systems/World.ts" +import { AddRobotsModal, AddFieldsModal, SpawningModal } from "@/modals/spawning/SpawningModals.tsx" +import ImportMirabufModal from "@/modals/mirabuf/ImportMirabufModal.tsx" +import ImportLocalMirabufModal from "@/modals/mirabuf/ImportLocalMirabufModal.tsx" import APS from "./aps/APS.ts" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -65,8 +65,8 @@ function Synthesis() { const code = urlParams.get("code") if (code) { APS.convertAuthToken(code).then(() => { - document.location.search = '' - }); + document.location.search = "" + }) } } const { openModal, closeModal, getActiveModalElement } = useModalManager(initialModals) @@ -83,7 +83,7 @@ function Synthesis() { const modalElement = getActiveModalElement() useEffect(() => { - if (has_code) return; + if (has_code) return World.InitWorld() diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 0d7d30cde0..78997d2bd4 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -6,14 +6,14 @@ const APS_USER_INFO_KEY = "aps_user_info" export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" -const CLIENT_ID = 'GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU' +const CLIENT_ID = "GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU" interface APSAuth { - access_token: string; - refresh_token: string; - expires_in: number; - expires_at: number; - token_type: number; + access_token: string + refresh_token: string + expires_in: number + expires_at: number + token_type: number } interface APSUserInfo { @@ -30,7 +30,7 @@ class APS { private static get auth(): APSAuth | undefined { const res = window.localStorage.getItem(APS_AUTH_KEY) try { - return res ? JSON.parse(res) : undefined; + return res ? JSON.parse(res) : undefined } catch (e) { console.warn(`Failed to parse stored APS auth data: ${e}`) return undefined @@ -46,13 +46,13 @@ class APS { } static async getAuth(): Promise { - const auth = this.auth; - if (!auth) return undefined; + const auth = this.auth + if (!auth) return undefined if (Date.now() > auth.expires_at) { - await this.refreshAuthToken(auth.refresh_token); + await this.refreshAuthToken(auth.refresh_token) } - return this.auth; + return this.auth } static get userInfo(): APSUserInfo | undefined { @@ -86,126 +86,151 @@ class APS { : `https://synthesis.autodesk.com${import.meta.env.BASE_URL}` try { - const challenge = await this.codeChallenge(); + const challenge = await this.codeChallenge() const params = new URLSearchParams({ - 'response_type': 'code', - 'client_id': CLIENT_ID, - 'redirect_uri': callbackUrl, - 'scope': 'data:read', - 'nonce': Date.now().toString(), - 'prompt': 'login', - 'code_challenge': challenge, - 'code_challenge_method': 'S256' + response_type: "code", + client_id: CLIENT_ID, + redirect_uri: callbackUrl, + scope: "data:read", + nonce: Date.now().toString(), + prompt: "login", + code_challenge: challenge, + code_challenge_method: "S256", }) if (APS.userInfo) { - params.append('authoptions', encodeURIComponent(JSON.stringify({ id: APS.userInfo.email }))); + params.append("authoptions", encodeURIComponent(JSON.stringify({ id: APS.userInfo.email }))) } - const url = `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`; + const url = `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}` - window.open(url, '_self') + window.open(url, "_self") } catch (e) { - console.error(e); - MainHUD_AddToast("error", "Error signing in.", "Please try again."); + console.error(e) + MainHUD_AddToast("error", "Error signing in.", "Please try again.") } }) } static async refreshAuthToken(refresh_token: string) { await this.requestMutex.runExclusive(async () => { - let retry_login = false; - const res = await fetch('https://developer.api.autodesk.com/authentication/v2/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'client_id': CLIENT_ID, - 'grant_type': 'refresh_token', - 'refresh_token': refresh_token, - 'scope': 'data:read', + try { + const res = await fetch("https://developer.api.autodesk.com/authentication/v2/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + client_id: CLIENT_ID, + grant_type: "refresh_token", + refresh_token: refresh_token, + scope: "data:read", + }), }) - }) - .then(res => res.json()) - .catch(e => { - console.error(e); - retry_login = true; - }); - if (retry_login) { - MainHUD_AddToast("error", "Error signing in.", "Please try again."); - this.auth = undefined; - this.requestAuthCode(); - } else { - res.expires_at = res.expires_in + Date.now() - this.auth = res as APSAuth + const json = await res.json() + if (!res.ok) { + MainHUD_AddToast("error", "Error signing in.", json.userMessage) + this.auth = undefined + await this.requestAuthCode() + return + } + json.expires_at = json.expires_in + Date.now() + this.auth = json as APSAuth if (this.auth) { - await this.loadUserInfo(this.auth); + await this.loadUserInfo(this.auth) if (APS.userInfo) { - MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`); + MainHUD_AddToast("info", "ADSK Login", `Hello, ${APS.userInfo.givenName}`) } } + } catch (e) { + MainHUD_AddToast("error", "Error signing in.", "Please try again.") + this.auth = undefined + await this.requestAuthCode() } }) } static async convertAuthToken(code: string) { - const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` : `https://synthesis.autodesk.com/api/aps/code/` - let retry_login = false; + const authUrl = import.meta.env.DEV + ? `http://localhost:3003/api/aps/code/` + : `https://synthesis.autodesk.com/api/aps/code/` + let retry_login = false try { - const res = await fetch(`${authUrl}?code=${code}`); + const res = await fetch(`${authUrl}?code=${code}`) const json = await res.json() - const auth_res = json.response as APSAuth; + if (!res.ok) { + MainHUD_AddToast("error", "Error signing in.", json.userMessage) + this.auth = undefined + return + } + const auth_res = json.response as APSAuth auth_res.expires_at = auth_res.expires_in + Date.now() this.auth = auth_res - console.log('Preloading user info') - const auth = await this.getAuth(); + console.log("Preloading user info") + const auth = await this.getAuth() if (auth) { - await this.loadUserInfo(auth); + await this.loadUserInfo(auth) if (APS.userInfo) { - MainHUD_AddToast('info', 'ADSK Login', `Hello, ${APS.userInfo.givenName}`) + MainHUD_AddToast("info", "ADSK Login", `Hello, ${APS.userInfo.givenName}`) } } else { - console.error("Couldn't get auth data."); - retry_login = true; + console.error("Couldn't get auth data.") + retry_login = true } } catch (e) { - console.error(e); + console.error(e) retry_login = true } if (retry_login) { - this.auth = undefined; - MainHUD_AddToast('error', 'Error signing in.', 'Please try again.'); + this.auth = undefined + MainHUD_AddToast("error", "Error signing in.", "Please try again.") } } static async loadUserInfo(auth: APSAuth) { console.log("Loading user information") - await fetch("https://api.userprofile.autodesk.com/userinfo", { - method: "GET", - headers: { - Authorization: auth.access_token, - }, - }) - .then(x => x.json()) - .then(x => { - const info: APSUserInfo = { - name: x.name, - givenName: x.given_name, - picture: x.picture, - email: x.email - } - - this.userInfo = info + try { + const res = await fetch("https://api.userprofile.autodesk.com/userinfo", { + method: "GET", + headers: { + Authorization: auth.access_token, + }, }) + const json = await res.json() + if (!res.ok) { + MainHUD_AddToast("error", "Error fetching user data.", json.userMessage) + this.auth = undefined + await this.requestAuthCode() + return + } + const info: APSUserInfo = { + name: json.name, + givenName: json.given_name, + picture: json.picture, + email: json.email, + } + + this.userInfo = info + } catch (e) { + console.error(e) + MainHUD_AddToast("error", "Error signing in.", "Please try again.") + this.auth = undefined + } } static async codeChallenge() { - const endpoint = import.meta.env.DEV ? 'http://localhost:3003/api/aps/challenge/' : 'https://synthesis.autodesk.com/api/aps/challenge/'; - const res = await fetch(endpoint) - const json = await res.json() - return json['challenge'] + try { + const endpoint = import.meta.env.DEV + ? "http://localhost:3003/api/aps/challenge/" + : "https://synthesis.autodesk.com/api/aps/challenge/" + const res = await fetch(endpoint) + const json = await res.json() + return json["challenge"] + } catch (e) { + console.error(e) + MainHUD_AddToast("error", "Error signing in.", "Please try again.") + } } } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index c2275266e9..d35088bbcf 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -35,17 +35,13 @@ const MainHUDButton: React.FC = ({ value, icon, onClick, larger }) className={`relative flex flex-row cursor-pointer bg-background w-full m-auto px-2 py-1 text-main-text border-none rounded-md ${larger ? "justify-center" : ""} items-center hover:brightness-105 focus:outline-0 focus-visible:outline-0`} > {larger && icon} - {!larger && ( - - {icon} - - )} + {!larger && {icon}} {value} ) } -export let MainHUD_AddToast: (type: ToastType, title: string, description: string) => void = (_a, _b, _c) => { } +export let MainHUD_AddToast: (type: ToastType, title: string, description: string) => void = (_a, _b, _c) => {} const variants = { open: { opacity: 1, y: "-50%", x: 0 }, @@ -125,11 +121,7 @@ const MainHUD: React.FC = () => { icon={} onClick={() => openModal("import-local-mirabuf")} /> - } - onClick={TestGodMode} - /> + } onClick={TestGodMode} /> } @@ -166,22 +158,21 @@ const MainHUD: React.FC = () => { }} />
- {userInfo - ? + {userInfo ? ( } larger={true} onClick={() => APS.logout()} /> - : + ) : ( } larger={true} onClick={() => APS.requestAuthCode()} /> - } + )} ) From f93d75e269b49f83e853bce62fb3f6248d97ba69 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 3 Jul 2024 13:44:17 -0700 Subject: [PATCH 12/13] fix references to private APS.auth and add JSDoc --- fission/src/Window.d.ts | 3 -- fission/src/aps/APS.ts | 57 ++++++++++++++++++++++++--- fission/src/aps/APSDataManagement.ts | 6 +-- fission/src/ui/components/MainHUD.tsx | 8 ++-- 4 files changed, 60 insertions(+), 14 deletions(-) delete mode 100644 fission/src/Window.d.ts diff --git a/fission/src/Window.d.ts b/fission/src/Window.d.ts deleted file mode 100644 index 89e3c41f21..0000000000 --- a/fission/src/Window.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare interface Window { - setAuthCode(code: string): void -} diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 78997d2bd4..ecdb23ea3c 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -45,7 +45,37 @@ class APS { this.userInfo = undefined } - static async getAuth(): Promise { + /** + * Sets the timestamp at which the access token expires + * + * @param {number} expires_at - When the token expires + */ + static setExpiresAt(expires_at: number) { + if (this.auth) + this.auth.expires_at = expires_at; + } + + /** + * Returns whether the user is signed in + * @returns {boolean} Whether the user is signed in + */ + static isSignedIn(): boolean { + return !!this.getAuth() + } + + /** + * Returns the auth data of the current user. See {@link APSAuth} + * @returns {(APSAuth | undefined)} Auth data of the current user + */ + static getAuth(): APSAuth | undefined { + return this.auth + } + + /** + * Returns the auth data of the current user or prompts them to sign in if they haven't. See {@link APSAuth} and {@link APS#refreshAuthToken} + * @returns {Promise} Promise that resolves to the auth data + */ + static async getAuthOrLogin(): Promise { const auth = this.auth if (!auth) return undefined @@ -75,10 +105,16 @@ class APS { document.dispatchEvent(new Event(APS_USER_INFO_UPDATE_EVENT)) } + /** + * Logs the user out by setting their auth data to undefined. + */ static async logout() { this.auth = undefined } + /** + * Prompts the user to sign in, which will retrieve the auth code. + */ static async requestAuthCode() { await this.requestMutex.runExclusive(async () => { const callbackUrl = import.meta.env.DEV @@ -113,6 +149,10 @@ class APS { }) } + /** + * Refreshes the access token using our refresh token. + * @param {string} refresh_token - The refresh token from our auth data + */ static async refreshAuthToken(refresh_token: string) { await this.requestMutex.runExclusive(async () => { try { @@ -151,6 +191,10 @@ class APS { }) } + /** + * Fetches the auth data from Autodesk using the auth code. + * @param {string} code - The auth code + */ static async convertAuthToken(code: string) { const authUrl = import.meta.env.DEV ? `http://localhost:3003/api/aps/code/` @@ -188,6 +232,10 @@ class APS { } } + /** + * Fetches user information using the auth data. See {@link APSAuth} + * @param {APSAuth} auth - The auth data + */ static async loadUserInfo(auth: APSAuth) { console.log("Loading user information") try { @@ -219,6 +267,9 @@ class APS { } } + /** + * Fetches the code challenge from our server for requesting the auth code. + */ static async codeChallenge() { try { const endpoint = import.meta.env.DEV @@ -234,8 +285,4 @@ class APS { } } -Window.prototype.setAuthCode = (code: string) => { - APS.authCode = code -} - export default APS diff --git a/fission/src/aps/APSDataManagement.ts b/fission/src/aps/APSDataManagement.ts index 0c6c4a6cec..833794583e 100644 --- a/fission/src/aps/APSDataManagement.ts +++ b/fission/src/aps/APSDataManagement.ts @@ -59,7 +59,7 @@ export class Item extends Data { } export async function getHubs(): Promise { - const auth = APS.auth + const auth = APS.getAuth() if (!auth) { return undefined } @@ -88,7 +88,7 @@ export async function getHubs(): Promise { } export async function getProjects(hub: Hub): Promise { - const auth = APS.auth + const auth = APS.getAuth() if (!auth) { return undefined } @@ -121,7 +121,7 @@ export async function getProjects(hub: Hub): Promise { } export async function getFolderData(project: Project, folder: Folder): Promise { - const auth = APS.auth + const auth = APS.getAuth() if (!auth) { return undefined } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index d35088bbcf..fb41a004e3 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -125,14 +125,16 @@ const MainHUD: React.FC = () => { } - onClick={() => APS.auth && APS.refreshAuthToken(APS.auth.refresh_token)} + onClick={() => APS.isSignedIn() && APS.refreshAuthToken(APS.getAuth()!.refresh_token)} /> } onClick={() => { - if (APS.auth) APS.auth.expires_at = Date.now() - APS.getAuth() + if (APS.isSignedIn()) { + APS.setExpiresAt(Date.now()) + APS.getAuthOrLogin() + } }} /> From fb390de4f9cd3da0d6f4da82d835e505c8e3de27 Mon Sep 17 00:00:00 2001 From: Hunter Barclay Date: Tue, 9 Jul 2024 11:32:21 -0600 Subject: [PATCH 13/13] Little bit of refactoring --- fission/.env | 2 ++ fission/src/Synthesis.tsx | 2 +- fission/src/aps/APS.ts | 23 ++++++++++++----------- fission/src/ui/components/MainHUD.tsx | 5 ++--- fission/src/vite-env.d.ts | 9 +++++++++ fission/vite.config.ts | 4 ++-- 6 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 fission/.env diff --git a/fission/.env b/fission/.env new file mode 100644 index 0000000000..f5c2b2b526 --- /dev/null +++ b/fission/.env @@ -0,0 +1,2 @@ +VITE_SYNTHESIS_SERVER_PATH=/ +# VITE_SYNTHESIS_SERVER_PATH=https://synthesis.autodesk.com/ \ No newline at end of file diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 3652d7b99b..c5e980c8d0 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -54,7 +54,7 @@ import World from "@/systems/World.ts" import { AddRobotsModal, AddFieldsModal, SpawningModal } from "@/modals/spawning/SpawningModals.tsx" import ImportMirabufModal from "@/modals/mirabuf/ImportMirabufModal.tsx" import ImportLocalMirabufModal from "@/modals/mirabuf/ImportLocalMirabufModal.tsx" -import APS from "./aps/APS.ts" +import APS, { ENDPOINT_SYNTHESIS_CHALLENGE } from "./aps/APS.ts" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index ecdb23ea3c..ff2430db36 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -8,6 +8,13 @@ export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" const CLIENT_ID = "GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU" +const ENDPOINT_SYNTHESIS_CODE = `${import.meta.env.VITE_SYNTHESIS_SERVER_PATH}api/aps/code` +export const ENDPOINT_SYNTHESIS_CHALLENGE = `${import.meta.env.VITE_SYNTHESIS_SERVER_PATH}api/aps/challenge` + +const ENDPOINT_AUTODESK_AUTHENTICATION_AUTHORIZE = "https://developer.api.autodesk.com/authentication/v2/authorize" +const ENDPOINT_AUTODESK_AUTHENTICATION_TOKEN = "https://developer.api.autodesk.com/authentication/v2/token" +const ENDPOINT_AUTODESK_USERINFO = "https://api.userprofile.autodesk.com/userinfo" + interface APSAuth { access_token: string refresh_token: string @@ -139,7 +146,7 @@ class APS { params.append("authoptions", encodeURIComponent(JSON.stringify({ id: APS.userInfo.email }))) } - const url = `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}` + const url = `${ENDPOINT_AUTODESK_AUTHENTICATION_AUTHORIZE}?${params.toString()}` window.open(url, "_self") } catch (e) { @@ -156,7 +163,7 @@ class APS { static async refreshAuthToken(refresh_token: string) { await this.requestMutex.runExclusive(async () => { try { - const res = await fetch("https://developer.api.autodesk.com/authentication/v2/token", { + const res = await fetch(ENDPOINT_AUTODESK_AUTHENTICATION_TOKEN, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", @@ -196,12 +203,9 @@ class APS { * @param {string} code - The auth code */ static async convertAuthToken(code: string) { - const authUrl = import.meta.env.DEV - ? `http://localhost:3003/api/aps/code/` - : `https://synthesis.autodesk.com/api/aps/code/` let retry_login = false try { - const res = await fetch(`${authUrl}?code=${code}`) + const res = await fetch(`${ENDPOINT_SYNTHESIS_CODE}?code=${code}`) const json = await res.json() if (!res.ok) { MainHUD_AddToast("error", "Error signing in.", json.userMessage) @@ -239,7 +243,7 @@ class APS { static async loadUserInfo(auth: APSAuth) { console.log("Loading user information") try { - const res = await fetch("https://api.userprofile.autodesk.com/userinfo", { + const res = await fetch(ENDPOINT_AUTODESK_USERINFO, { method: "GET", headers: { Authorization: auth.access_token, @@ -272,10 +276,7 @@ class APS { */ static async codeChallenge() { try { - const endpoint = import.meta.env.DEV - ? "http://localhost:3003/api/aps/challenge/" - : "https://synthesis.autodesk.com/api/aps/challenge/" - const res = await fetch(endpoint) + const res = await fetch(ENDPOINT_SYNTHESIS_CHALLENGE) const json = await res.json() return json["challenge"] } catch (e) { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index fb41a004e3..e5a04175ec 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -12,7 +12,7 @@ import { motion } from "framer-motion" import logo from "@/assets/autodesk_logo.png" import { ToastType, useToastContext } from "@/ui/ToastContext" import { Random } from "@/util/Random" -import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" +import APS, { APS_USER_INFO_UPDATE_EVENT, ENDPOINT_SYNTHESIS_CHALLENGE } from "@/aps/APS" import { UserIcon } from "./UserIcon" import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" @@ -49,8 +49,6 @@ const variants = { } const MainHUD: React.FC = () => { - // console.debug('Creating MainHUD'); - const { openModal } = useModalControlContext() const { openPanel } = usePanelControlContext() const { addToast } = useToastContext() @@ -131,6 +129,7 @@ const MainHUD: React.FC = () => { value={"Expire APS Token"} icon={} onClick={() => { + return; if (APS.isSignedIn()) { APS.setExpiresAt(Date.now()) APS.getAuthOrLogin() diff --git a/fission/src/vite-env.d.ts b/fission/src/vite-env.d.ts index 11f02fe2a0..007c4f0852 100644 --- a/fission/src/vite-env.d.ts +++ b/fission/src/vite-env.d.ts @@ -1 +1,10 @@ /// + +interface ImportMetaEnv { + readonly VITE_SYNTHESIS_SERVER_PATH: string + // more env variables... + } + + interface ImportMeta { + readonly env: ImportMetaEnv + } \ No newline at end of file diff --git a/fission/vite.config.ts b/fission/vite.config.ts index f0734bca9f..4351139f35 100644 --- a/fission/vite.config.ts +++ b/fission/vite.config.ts @@ -4,7 +4,7 @@ import react from '@vitejs/plugin-react-swc' const basePath = '/fission/' const serverPort = 3000 -const dockerServerPort = 3003 +const dockerServerPort = 80 // https://vitejs.dev/config/ export default defineConfig({ @@ -40,7 +40,7 @@ export default defineConfig({ secure: false, rewrite: (path) => path.replace(/^\/api\/mira/, '/Downloadables/Mira') }, - '/api/auth': { + '/api/aps': { target: `http://localhost:${dockerServerPort}/`, changeOrigin: true, secure: false