aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/auth.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/auth.ts')
-rw-r--r--src/api/auth.ts34
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);