diff options
Diffstat (limited to 'src/api/auth.ts')
-rw-r--r-- | src/api/auth.ts | 34 |
1 files changed, 31 insertions, 3 deletions
diff --git a/src/api/auth.ts b/src/api/auth.ts index c9e3634..11baaa6 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,10 +1,14 @@ import Cookies, { CookieSetOptions } from "universal-cookie"; import { AxiosResponse } from "axios"; +import { startAuthorizing, finishAuthorizing } from "../slices/authorization"; +import formsStore from "../store"; + import APIClient from "./client"; const OAUTH2_CLIENT_ID = process.env.REACT_APP_OAUTH2_CLIENT_ID; const PRODUCTION = process.env.NODE_ENV !== "development"; +const STATE_LENGTH = 64; /** * Authorization result as returned from the backend. @@ -32,6 +36,7 @@ export enum APIErrorMessages { BackendValidationDev = "Backend could not authorize with Discord, possibly due to being on a preview branch. Please contact the forms team.", BackendUnresponsive = "Unable to reach the backend, please retry, or contact the forms team.", BadResponse = "The server returned a bad response, please contact the forms team.", + AccessRejected = "Authorization was cancelled.", Unknown = "An unknown error occurred, please contact the forms team." } @@ -94,25 +99,40 @@ export function checkScopes(scopes?: OAuthScopes[]): boolean { * @returns {code, cleanedScopes} The discord authorization code and the scopes the code is granted for. * @throws {Error} Indicates that an integrity check failed. */ -export async function getDiscordCode(scopes: OAuthScopes[], disableFunction?: (disable: boolean) => void): Promise<{code: string, cleanedScopes: OAuthScopes[]}> { +export async function getDiscordCode(scopes: OAuthScopes[], disableFunction?: (disable: boolean) => void): Promise<{code: string | null, cleanedScopes: OAuthScopes[]}> { const cleanedScopes = ensureMinimumScopes(scopes, OAuthScopes.Identify); // Generate a new user state - const state = crypto.getRandomValues(new Uint32Array(1))[0]; + const stateBytes = new Uint8Array(STATE_LENGTH); + crypto.getRandomValues(stateBytes); + + let state = ""; + for (let i = 0; i < stateBytes.length; i++) { + state += stateBytes[i].toString(16).padStart(2, "0"); + } const scopeString = encodeURIComponent(cleanedScopes.join(" ")); const redirectURI = encodeURIComponent(document.location.protocol + "//" + document.location.host + "/callback"); + const windowHeight = screen.availHeight; + const windowWidth = screen.availWidth; + const requestHeight = Math.floor(windowHeight * 0.75); + const requestWidth = Math.floor(windowWidth * 0.4); + // Open login window const windowRef = window.open( `https://discord.com/api/oauth2/authorize?client_id=${OAUTH2_CLIENT_ID}&state=${state}&response_type=code&scope=${scopeString}&redirect_uri=${redirectURI}&prompt=consent`, - "Discord_OAuth2" + "_blank", + `popup=true,height=${requestHeight},left=0,top=0,width=${requestWidth}` ); + formsStore.dispatch(startAuthorizing()); + // Clean up on login const interval = setInterval(() => { if (windowRef?.closed) { clearInterval(interval); + formsStore.dispatch(finishAuthorizing()); if (disableFunction) { disableFunction(false); } } }, 500); @@ -133,6 +153,8 @@ export async function getDiscordCode(scopes: OAuthScopes[], disableFunction?: (d if (message.isTrusted) { windowRef?.close(); + formsStore.dispatch(finishAuthorizing()); + clearInterval(interval); // State integrity check @@ -246,6 +268,12 @@ export default async function authorize(scopes: OAuthScopes[] = [], disableFunct if (disableFunction) { disableFunction(true); } await getDiscordCode(scopes, disableFunction).then(async discord_response =>{ + if (!discord_response.code) { + throw { + Message: APIErrorMessages.AccessRejected, + Error: null + }; + } await requestBackendJWT(discord_response.code).then(backend_response => { const options: CookieSetOptions = {sameSite: "strict", secure: PRODUCTION, path: "/", expires: new Date(3000, 1)}; cookies.set(CookieNames.Username, backend_response.username, options); |