Skip to content

Commit

Permalink
Merge pull request #4472 from coralproject/spike/graceful-auth-expiry
Browse files Browse the repository at this point in the history
[CORL-3008] when auth token is invalid, clear it and reload the stream
  • Loading branch information
nick-funk authored Jan 11, 2024
2 parents 1a74b02 + 4b16a9e commit 5940b09
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 6 deletions.
2 changes: 1 addition & 1 deletion client/src/core/client/framework/lib/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Claims, computeExpiresIn, parseAccessTokenClaims } from "./helpers";
/**
* ACCESS_TOKEN_KEY is the key in storage where the accessToken is stored.
*/
const ACCESS_TOKEN_KEY = "v2:accessToken";
export const ACCESS_TOKEN_KEY = "v2:accessToken";

export interface AuthState {
/**
Expand Down
20 changes: 18 additions & 2 deletions client/src/core/client/framework/lib/bootstrap/createManaged.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ interface CreateContextArguments {
* of the render window
*/
customScrollContainer?: HTMLElement;

/**
* called when a token is invalid and we are unable to login so we can
* clear the local auth token allowing a user to login again instead of
* throwing a network error that is impassable.
*/
onAuthError?: () => void;
}

/**
Expand Down Expand Up @@ -142,6 +149,7 @@ function createRelayEnvironment(
clientID: string,
localeBundles: FluentBundle[],
tokenRefreshProvider: TokenRefreshProvider,
onAuthError?: () => void,
clearCacheBefore?: Date
) {
const source = new RecordSource();
Expand All @@ -160,6 +168,7 @@ function createRelayEnvironment(
clientID,
accessTokenProvider,
localeBundles,
onAuthError,
tokenRefreshProvider.refreshToken,
clearCacheBefore
),
Expand Down Expand Up @@ -203,6 +212,8 @@ function createManagedCoralContextProvider(
clientID: string,
initLocalState: InitLocalState,
localesData: LocalesData,
localStorage: PromisifiedStorage<string>,
onAuthError?: () => void,
ErrorBoundary?: React.ComponentType<{ children?: React.ReactNode }>,
refreshAccessTokenPromise?: RefreshAccessTokenPromise,
staticConfig?: StaticConfig | null
Expand Down Expand Up @@ -270,6 +281,7 @@ function createManagedCoralContextProvider(
clientID,
this.state.context.localeBundles,
this.state.context.tokenRefreshProvider,
onAuthError,
// Disable the cache on requests for the next 30 seconds.
new Date(Date.now() + 30 * 1000)
);
Expand Down Expand Up @@ -339,7 +351,7 @@ function createManagedCoralContextProvider(
/*
* resolveStorage decides which storage to use in the context
*/
function resolveStorage(
export function resolveStorage(
type: "localStorage" | "sessionStorage" | "indexedDB"
): PromisifiedStorage {
switch (type) {
Expand Down Expand Up @@ -397,6 +409,7 @@ export default async function createManaged({
refreshAccessTokenPromise,
staticConfig = getStaticConfig(window),
customScrollContainer,
onAuthError,
}: CreateContextArguments): Promise<
ComponentType<{ children?: React.ReactNode }>
> {
Expand Down Expand Up @@ -468,7 +481,8 @@ export default async function createManaged({
subscriptionClient,
clientID,
localeBundles,
tokenRefreshProvider
tokenRefreshProvider,
onAuthError
);

// Assemble context.
Expand Down Expand Up @@ -522,6 +536,8 @@ export default async function createManaged({
clientID,
initLocalState,
localesData,
localStorage,
onAuthError,
reporter?.ErrorBoundary,
refreshAccessTokenPromise,
staticConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function createNetwork(
clientID: string,
accessTokenProvider: AccessTokenProvider,
localeBundles: FluentBundle[],
onAuthError?: () => void,
tokenRefresh?: TokenRefresh,
clearCacheBefore?: Date
) {
Expand All @@ -78,7 +79,11 @@ export default function createNetwork(
retryDelays: (attempt: number) => Math.pow(2, attempt + 4) * 100,
// or simple array [3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600],
statusCodes: [500, 503, 504],
beforeRetry: ({ abort, attempt, lastError }) => {
beforeRetry: async ({ abort, attempt, lastError }) => {
if (lastError?.name === "Missing Auth Error" && onAuthError) {
onAuthError();
}

if (attempt > 2) {
let message = lastError?.message;
if (message && lastError?.name !== "RRNLRetryMiddlewareError") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ShowAuthPopupMutation,
waitTillAuthPopupIsClosed,
} from "../../common/AuthPopup";
import { MissingAuthError } from "./missingAuthError";

const authControlQuery = graphql`
query RefreshTokenHandlerAuthControlQuery {
Expand Down Expand Up @@ -59,7 +60,7 @@ const RefreshTokenHandler: FunctionComponent = () => {
);

if (!data?.settings?.auth) {
throw new Error(
throw new MissingAuthError(
"Missing auth data. Make sure <UserBoxContainer /> has been rendered."
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class MissingAuthError extends Error {
public readonly name: string;

constructor(message: string | undefined) {
super(message);

this.name = "Missing Auth Error";
}
}
15 changes: 14 additions & 1 deletion client/src/core/client/stream/stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { LanguageCode } from "coral-common/common/lib/helpers/i18n/locales";
import { parseQuery } from "coral-common/common/lib/utils";
import { RefreshAccessTokenCallback } from "coral-embed/Coral";
import { createManaged } from "coral-framework/lib/bootstrap";
import { RefreshAccessTokenPromise } from "coral-framework/lib/bootstrap/createManaged";
import {
RefreshAccessTokenPromise,
resolveStorage,
} from "coral-framework/lib/bootstrap/createManaged";
import {
CSSAsset,
EncapsulationContext,
Expand All @@ -28,6 +31,7 @@ import localesData from "./locales";
import { EmotionShadowRoot } from "./shadow";

// Import css variables.
import { ACCESS_TOKEN_KEY } from "coral-framework/lib/auth";
import "coral-ui/theme/streamEmbed.css";
import "coral-ui/theme/typography.css";

Expand Down Expand Up @@ -99,6 +103,13 @@ export async function attach(options: AttachOptions) {
await new Promise((resolve) => options.refreshAccessToken!(resolve));
}

const onContextAuthError = async () => {
const localStorage = resolveStorage("localStorage");
await localStorage.removeItem(ACCESS_TOKEN_KEY);
await remove(options.element);
await attach(options);
};

const ManagedCoralContextProvider = await createManaged({
rootURL: options.rootURL,
lang: options.locale,
Expand All @@ -111,6 +122,7 @@ export async function attach(options: AttachOptions) {
refreshAccessTokenPromise,
staticConfig: options.staticConfig,
customScrollContainer: options.customScrollContainer,
onAuthError: onContextAuthError,
});

// Amount of initial css files to be loaded.
Expand All @@ -134,6 +146,7 @@ export async function attach(options: AttachOptions) {
// flash of unstyled content.
const [isCSSLoaded, setIsCSSLoaded] = useState(false);
const [loadError, setLoadError] = useState(false);

const handleLoadError = useCallback((href: string) => {
globalErrorReporter.report(
// encode href, otherwise sentry will not send it.
Expand Down

0 comments on commit 5940b09

Please sign in to comment.