* A modal used to collect demographics information Add checkmark to final page (#24957) Remove themeing to get ready for staging MICROBA-574 | Dismiss CTA after learner finishes answering modal questions (#24965) [MICROBA-574] - Dismiss CTA after learner finishes answering demographics questions - Cleanup comments * Various initial bugfixes - fixes 2 issues with the multiselect dropdown erasing state - prevents input higher than 255 characters in the self describe - fixes 400 errors when the user selects a default option - Removes additional page count section - Re-adding deleted JS file. Can't clean this up until after we cutover to using the new Demographics modal * Add translatable string to clear button * Remove extra page counter from the header for the third time * Remove unneeded template context Co-authored-by: Matt Tuchfarber <mtuchfarber@edx.org> Co-authored-by: Justin Hynes <jhynes@edx.org>
125 lines
3.9 KiB
JavaScript
125 lines
3.9 KiB
JavaScript
/**
|
|
* Service class to support JWT Token Authentication.
|
|
*
|
|
* Temporarily copied from the edx/frontend-platform
|
|
*/
|
|
import Cookies from 'universal-cookie';
|
|
import jwtDecode from 'jwt-decode';
|
|
import axios from 'axios';
|
|
import createRetryInterceptor from './interceptors/createRetryInterceptor';
|
|
import { processAxiosErrorAndThrow } from './utils';
|
|
|
|
export default class AxiosJwtTokenService {
|
|
static isTokenExpired(token) {
|
|
return !token || token.exp < Date.now() / 1000;
|
|
}
|
|
|
|
constructor(tokenCookieName, tokenRefreshEndpoint) {
|
|
this.tokenCookieName = tokenCookieName;
|
|
this.tokenRefreshEndpoint = tokenRefreshEndpoint;
|
|
|
|
this.httpClient = axios.create();
|
|
// Set withCredentials to true. Enables cross-site Access-Control requests
|
|
// to be made using cookies, authorization headers or TLS client
|
|
// certificates. More on MDN:
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
|
|
this.httpClient.defaults.withCredentials = true;
|
|
// Add retries to this axios instance
|
|
this.httpClient.interceptors.response.use(
|
|
response => response,
|
|
createRetryInterceptor({ httpClient: this.httpClient }),
|
|
);
|
|
|
|
this.cookies = new Cookies();
|
|
this.refreshRequestPromises = {};
|
|
}
|
|
|
|
getHttpClient() {
|
|
return this.httpClient;
|
|
}
|
|
|
|
decodeJwtCookie() {
|
|
const cookieValue = this.cookies.get(this.tokenCookieName);
|
|
|
|
if (cookieValue) {
|
|
try {
|
|
return jwtDecode(cookieValue);
|
|
} catch (e) {
|
|
const error = Object.create(e);
|
|
error.message = 'Error decoding JWT token';
|
|
error.customAttributes = { cookieValue };
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
refresh() {
|
|
if (this.refreshRequestPromises[this.tokenCookieName] === undefined) {
|
|
const makeRefreshRequest = async () => {
|
|
let axiosResponse;
|
|
try {
|
|
try {
|
|
axiosResponse = await this.httpClient.post(this.tokenRefreshEndpoint);
|
|
} catch (error) {
|
|
processAxiosErrorAndThrow(error);
|
|
}
|
|
} catch (error) {
|
|
const userIsUnauthenticated = error.response && error.response.status === 401;
|
|
if (userIsUnauthenticated) {
|
|
// Clean up the cookie if it exists to eliminate any situation
|
|
// where the cookie is not expired but the jwt is expired.
|
|
this.cookies.remove(this.tokenCookieName);
|
|
const decodedJwtToken = null;
|
|
return decodedJwtToken;
|
|
}
|
|
|
|
// TODO: Network timeouts and other problems will end up in
|
|
// this block of code. We could add logic for retrying token
|
|
// refreshes if we wanted to.
|
|
throw error;
|
|
}
|
|
|
|
const decodedJwtToken = this.decodeJwtCookie();
|
|
|
|
if (!decodedJwtToken) {
|
|
// This is an unexpected case. The refresh endpoint should
|
|
// set the cookie that is needed. See ARCH-948 for more
|
|
// information on a similar situation that was happening
|
|
// prior to this refactor in Oct 2019.
|
|
const error = new Error('Access token is still null after successful refresh.');
|
|
error.customAttributes = { axiosResponse };
|
|
throw error;
|
|
}
|
|
|
|
return decodedJwtToken;
|
|
};
|
|
|
|
this.refreshRequestPromises[this.tokenCookieName] = makeRefreshRequest().finally(() => {
|
|
delete this.refreshRequestPromises[this.tokenCookieName];
|
|
});
|
|
}
|
|
|
|
return this.refreshRequestPromises[this.tokenCookieName];
|
|
}
|
|
|
|
async getJwtToken() {
|
|
try {
|
|
const decodedJwtToken = this.decodeJwtCookie(this.tokenCookieName);
|
|
if (!AxiosJwtTokenService.isTokenExpired(decodedJwtToken)) {
|
|
return decodedJwtToken;
|
|
}
|
|
} catch (e) {
|
|
// Log unexpected error and continue with attempt to refresh it.
|
|
throw e;
|
|
}
|
|
|
|
try {
|
|
return await this.refresh();
|
|
} catch (e) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|