Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ export class AsgardeoSPAClient {
if (config.signInRequired) {
await this._validateMethod();
} else {
await this._validateMethod();
// await this._validateMethod();
}

if (!config.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const MainThreadClient = async (

let _getSignOutURLFromSessionStorage: boolean = false;

const _httpClient: HttpClientInstance = HttpClient.getInstance();
const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceID);
let _isHttpHandlerEnabled: boolean = true;
let _httpErrorCallback: (error: HttpError) => void | Promise<void>;
let _httpFinishCallback: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ import {HttpClientInstance, HttpClientInterface, HttpClientStatic} from '../mode
*/
@staticDecorator<HttpClientStatic<HttpClientInstance>>()
export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpResponse, HttpError> {
private static axiosInstance: HttpClientInstance;
private static clientInstance: HttpClient;
private static isHandlerEnabled: boolean;
private static instances: Map<number, HttpClientInstance> = new Map();
private static clientInstances: Map<number, HttpClient> = new Map();
private isHandlerEnabled: boolean;
private attachToken: (request: HttpRequestConfig) => Promise<void> = () => Promise.resolve();
private requestStartCallback: (request: HttpRequestConfig) => void = () => null;
private requestSuccessCallback: (response: HttpResponse) => void = () => null;
Expand All @@ -66,48 +66,51 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
}

/**
* Returns an aggregated instance of type `HttpInstance` of `HttpClient`.
* Returns an instance-specific HttpClient instance.
* Each instance ID gets its own axios instance and HttpClient to avoid state conflicts.
*
* @return {any}
* @param instanceId - The instance ID for multi-auth context support. Defaults to 0.
* @return {HttpClientInstance}
*/
public static getInstance(): HttpClientInstance {
if (this.axiosInstance) {
return this.axiosInstance;
public static getInstance(instanceId: number = 0): HttpClientInstance {
if (this.instances.has(instanceId)) {
return this.instances.get(instanceId)!;
}

this.axiosInstance = axios.create({
const axiosInstance = axios.create({
withCredentials: true,
});
}) as HttpClientInstance;

if (!this.clientInstance) {
this.clientInstance = new HttpClient();
}
const clientInstance = new HttpClient();
this.clientInstances.set(instanceId, clientInstance);

// Register request interceptor
this.axiosInstance.interceptors.request.use(async (request: InternalAxiosRequestConfig) => await this.clientInstance.requestHandler(request as HttpRequestConfig) as InternalAxiosRequestConfig);
axiosInstance.interceptors.request.use(async (request: InternalAxiosRequestConfig) => await clientInstance.requestHandler(request as HttpRequestConfig) as InternalAxiosRequestConfig);

// Register response interceptor
this.axiosInstance.interceptors.response.use(
response => this.clientInstance.successHandler(response),
error => this.clientInstance.errorHandler(error),
axiosInstance.interceptors.response.use(
response => clientInstance.successHandler(response),
error => clientInstance.errorHandler(error),
);

// Add the missing helper methods from axios
this.axiosInstance.all = axios.all;
this.axiosInstance.spread = axios.spread;
axiosInstance.all = axios.all;
axiosInstance.spread = axios.spread;

// Add the init method from the `HttpClient` instance.
this.axiosInstance.init = this.clientInstance.init;
axiosInstance.init = clientInstance.init;

// Add the handler enabling & disabling methods to the instance.
this.axiosInstance.enableHandler = this.clientInstance.enableHandler;
this.axiosInstance.disableHandler = this.clientInstance.disableHandler;
this.axiosInstance.disableHandlerWithTimeout = this.clientInstance.disableHandlerWithTimeout;
this.axiosInstance.setHttpRequestStartCallback = this.clientInstance.setHttpRequestStartCallback;
this.axiosInstance.setHttpRequestSuccessCallback = this.clientInstance.setHttpRequestSuccessCallback;
this.axiosInstance.setHttpRequestErrorCallback = this.clientInstance.setHttpRequestErrorCallback;
this.axiosInstance.setHttpRequestFinishCallback = this.clientInstance.setHttpRequestFinishCallback;
return this.axiosInstance;
axiosInstance.enableHandler = clientInstance.enableHandler;
axiosInstance.disableHandler = clientInstance.disableHandler;
axiosInstance.disableHandlerWithTimeout = clientInstance.disableHandlerWithTimeout;
axiosInstance.setHttpRequestStartCallback = clientInstance.setHttpRequestStartCallback;
axiosInstance.setHttpRequestSuccessCallback = clientInstance.setHttpRequestSuccessCallback;
axiosInstance.setHttpRequestErrorCallback = clientInstance.setHttpRequestErrorCallback;
axiosInstance.setHttpRequestFinishCallback = clientInstance.setHttpRequestFinishCallback;

this.instances.set(instanceId, axiosInstance);
return axiosInstance;
}

/**
Expand All @@ -134,7 +137,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe

request.startTimeInMs = new Date().getTime();

if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestStartCallback && typeof this.requestStartCallback === 'function') {
this.requestStartCallback(request);
}
Expand All @@ -151,7 +154,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @return {HttpError}
*/
public errorHandler(error: HttpError): HttpError {
if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestErrorCallback && typeof this.requestErrorCallback === 'function') {
this.requestErrorCallback(error);
}
Expand All @@ -171,7 +174,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @return {HttpResponse}
*/
public successHandler(response: HttpResponse): HttpResponse {
if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestSuccessCallback && typeof this.requestSuccessCallback === 'function') {
this.requestSuccessCallback(response);
}
Expand All @@ -195,22 +198,22 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
isHandlerEnabled = true,
attachToken: (request: HttpRequestConfig) => Promise<void>,
): Promise<void> {
HttpClient.isHandlerEnabled = isHandlerEnabled;
this.isHandlerEnabled = isHandlerEnabled;
this.attachToken = attachToken;
}

/**
* Enables the handler.
*/
public enableHandler(): void {
HttpClient.isHandlerEnabled = true;
this.isHandlerEnabled = true;
}

/**
* Disables the handler.
*/
public disableHandler(): void {
HttpClient.isHandlerEnabled = false;
this.isHandlerEnabled = false;
}

/**
Expand All @@ -219,10 +222,10 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @param {number} timeout - Timeout in milliseconds.
*/
public disableHandlerWithTimeout(timeout: number = HttpClient.DEFAULT_HANDLER_DISABLE_TIMEOUT): void {
HttpClient.isHandlerEnabled = false;
this.isHandlerEnabled = false;

setTimeout(() => {
HttpClient.isHandlerEnabled = true;
this.isHandlerEnabled = true;
}, timeout);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { HttpError, HttpResponse } from "../../models";
* Http client interface with static functions.
*/
export interface HttpClientStatic<S> {
getInstance(): S;
getInstance(instanceId?: number): S;
}

/**
Expand Down
17 changes: 13 additions & 4 deletions packages/javascript/src/StorageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,17 @@ class StorageManager<T> {
await this.store.setData(key, dataToBeSavedJSON);
}

protected resolveKey(store: Stores | string, userId?: string): string {
return userId ? `${store}-${this.id}-${userId}` : `${store}-${this.id}`;
protected resolveKey(store: Stores | string, userId?: string, instanceId?: string): string {
if (userId && instanceId) {
return `${store}-${instanceId}-${userId}`;
}
if (userId) {
return `${store}-${this.id}-${userId}`;
}
if (instanceId) {
return `${store}-${instanceId}`;
}
return `${store}-${this.id}`;
}

protected static isLocalStorageAvailable(): boolean {
Expand Down Expand Up @@ -124,8 +133,8 @@ class StorageManager<T> {
return JSON.parse((await this.store.getData(this.resolveKey(Stores.TemporaryData, userId))) ?? null);
}

public async getSessionData(userId?: string): Promise<SessionData> {
return JSON.parse((await this.store.getData(this.resolveKey(Stores.SessionData, userId))) ?? null);
public async getSessionData(userId?: string, instanceId?: string): Promise<SessionData> {
return JSON.parse((await this.store.getData(this.resolveKey(Stores.SessionData, userId, instanceId))) ?? null);
}

public async getCustomData<K>(key: string, userId?: string): Promise<K> {
Expand Down
126 changes: 69 additions & 57 deletions packages/javascript/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,77 +914,89 @@ export class AsgardeoAuthClient<T> {
* @preserve
*/
public async exchangeToken(config: TokenExchangeRequestConfig, userId?: string): Promise<TokenResponse | Response> {
const oidcProviderMetadata: OIDCDiscoveryApiResponse = await this.oidcProviderMetaDataProvider();
const configData: StrictAuthClientConfig = await this.configProvider();
const executeTokenExchange = async (): Promise<TokenResponse | Response> => {
const oidcProviderMetadata: OIDCDiscoveryApiResponse = await this.oidcProviderMetaDataProvider();
const configData: StrictAuthClientConfig = await this.configProvider();

let tokenEndpoint: string | undefined;
let tokenEndpoint: string | undefined;

if (config.tokenEndpoint && config.tokenEndpoint.trim().length !== 0) {
tokenEndpoint = config.tokenEndpoint;
} else {
tokenEndpoint = oidcProviderMetadata.token_endpoint;
}
if (config.tokenEndpoint && config.tokenEndpoint.trim().length !== 0) {
tokenEndpoint = config.tokenEndpoint;
} else {
tokenEndpoint = oidcProviderMetadata.token_endpoint;
}

if (!tokenEndpoint || tokenEndpoint.trim().length === 0) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-NF01',
'Token endpoint not found.',
'No token endpoint was found in the OIDC provider meta data returned by the well-known endpoint ' +
'or the token endpoint passed to the SDK is empty.',
);
}
if (!tokenEndpoint || tokenEndpoint.trim().length === 0) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-NF01',
'Token endpoint not found.',
'No token endpoint was found in the OIDC provider meta data returned by the well-known endpoint ' +
'or the token endpoint passed to the SDK is empty.',
);
}

const data: string[] = await Promise.all(
Object.entries(config.data).map(async ([key, value]: [key: string, value: any]) => {
const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags(value as string, userId);
const data: string[] = await Promise.all(
Object.entries(config.data).map(async ([key, value]: [key: string, value: any]) => {
const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags(value as string, userId);

return `${key}=${newValue}`;
}),
);
return `${key}=${newValue}`;
}),
);

let requestHeaders: Record<string, any> = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};
let requestHeaders: Record<string, any> = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};

if (config.attachToken) {
requestHeaders = {
...requestHeaders,
Authorization: `Bearer ${(await this.storageManager.getSessionData(userId)).access_token}`,
if (config.attachToken) {
requestHeaders = {
...requestHeaders,
Authorization: `Bearer ${(await this.storageManager.getSessionData(userId)).access_token}`,
};
}

const requestConfig: RequestInit = {
body: data.join('&'),
credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin',
headers: new Headers(requestHeaders),
method: 'POST',
};
}

const requestConfig: RequestInit = {
body: data.join('&'),
credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin',
headers: new Headers(requestHeaders),
method: 'POST',
};
let response: Response;

let response: Response;
try {
response = await fetch(tokenEndpoint, requestConfig);
} catch (error: any) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-NE02',
'The custom grant request failed.',
error ?? 'The request sent to get the custom grant failed.',
);
}

try {
response = await fetch(tokenEndpoint, requestConfig);
} catch (error: any) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-NE02',
'The custom grant request failed.',
error ?? 'The request sent to get the custom grant failed.',
);
}
if (response.status !== 200 || !response.ok) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-HE03',
`Invalid response status received for the custom grant request. (${response.statusText})`,
(await response.json()) as string,
);
}

if (response.status !== 200 || !response.ok) {
throw new AsgardeoAuthException(
'JS-AUTH_CORE-RCG-HE03',
`Invalid response status received for the custom grant request. (${response.statusText})`,
(await response.json()) as string,
);
}
if (config.returnsSession) {
return this.authHelper.handleTokenResponse(response, userId);
}
return Promise.resolve((await response.json()) as TokenResponse | Response);
};

if (config.returnsSession) {
return this.authHelper.handleTokenResponse(response, userId);
if (
await this.storageManager.getTemporaryDataParameter(
OIDCDiscoveryConstants.Storage.StorageKeys.OPENID_PROVIDER_CONFIG_INITIATED,
)
) {
return executeTokenExchange();
}
return Promise.resolve((await response.json()) as TokenResponse | Response);

return this.loadOpenIDProviderConfiguration(false).then(() => executeTokenExchange());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,31 @@ export class AuthenticationHelper<T> {

public async replaceCustomGrantTemplateTags(text: string, userId?: string): Promise<string> {
const configData: StrictAuthClientConfig = await this.config();
const sessionData: SessionData = await this.storageManager.getSessionData(userId);

const sourceInstanceId: string | number | null = configData.organizationChain?.sourceInstanceId ?? null;

let sessionData: SessionData;

if (sourceInstanceId) {
const {clientId} = configData;
let instanceKey: string;
if (clientId) {
instanceKey = `instance_${sourceInstanceId}-${clientId}`;
} else {
instanceKey = `instance_${sourceInstanceId}`;
}
sessionData = await this.storageManager.getSessionData(userId, instanceKey);

if (!sessionData.access_token) {
throw new AsgardeoAuthException(
'JS-AUTH_HELPER-RCGTT-NE01',
'No session data found for source instance.',
'Failed to retrieve session data from the source organization context.',
);
}
} else {
sessionData = await this.storageManager.getSessionData(userId);
}

const scope: string = processOpenIDScopes(configData.scopes);

Expand Down
Loading
Loading