Skip to content

Commit f5739d1

Browse files
committed
feat: add getSchemas API function and related documentation; refactor Asgardeo client to integrate schema retrieval; enhance error handling and logging in authentication flows
1 parent 13ad6a1 commit f5739d1

File tree

18 files changed

+313
-16
lines changed

18 files changed

+313
-16
lines changed

packages/javascript/src/IsomorphicCrypto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class IsomorphicCrypto<T = any> {
138138
*/
139139
public decodeIdToken(idToken: string): IdToken {
140140
try {
141+
console.log('[IsomorphicCrypto] Decoding ID token:', idToken);
141142
const utf8String: string = this._cryptoUtils.base64URLDecode(idToken?.split('.')[1]);
142143
const payload: IdToken = JSON.parse(utf8String);
143144

packages/javascript/src/__legacy__/client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,14 @@ export class AsgardeoAuthClient<T> {
237237
await this._storageManager.setTemporaryDataParameter(pkceKey, codeVerifier, userId);
238238
}
239239

240-
console.log('[AsgardeoAuthClient] configData:', configData);
240+
if (authRequestConfig['client_secret']) {
241+
authRequestConfig['client_secret'] = configData.clientSecret;
242+
}
241243

242244
const authorizeRequestParams: Map<string, string> = getAuthorizeRequestUrlParams(
243245
{
244246
redirectUri: configData.afterSignInUrl,
245247
clientId: configData.clientId,
246-
clientSecret: configData.clientSecret,
247248
scopes: processOpenIDScopes(configData.scopes),
248249
responseMode: configData.responseMode,
249250
codeChallengeMethod: PKCEConstants.DEFAULT_CODE_CHALLENGE_METHOD,
@@ -360,6 +361,8 @@ export class AsgardeoAuthClient<T> {
360361

361362
let tokenResponse: Response;
362363

364+
console.log('[AsgardeoAuthClient] Requesting access token from:', tokenEndpoint);
365+
363366
try {
364367
tokenResponse = await fetch(tokenEndpoint, {
365368
body: body,
@@ -631,7 +634,9 @@ export class AsgardeoAuthClient<T> {
631634
* @preserve
632635
*/
633636
public async getUser(userId?: string): Promise<User> {
637+
console.log('[AsgardeoAuthClient] Getting user with userId:', userId);
634638
const sessionData: SessionData = await this._storageManager.getSessionData(userId);
639+
console.log('[AsgardeoAuthClient] Session data:', sessionData);
635640
const authenticatedUser: User = this._authenticationHelper.getAuthenticatedUserInfo(sessionData?.id_token);
636641

637642
Object.keys(authenticatedUser).forEach((key: string) => {

packages/javascript/src/__legacy__/helpers/authentication-helper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ export class AuthenticationHelper<T> {
288288

289289
await this._storageManager.setSessionData(parsedResponse, userId);
290290

291+
console.log('[AuthenticationHelper] Token response handled successfully:', userId, tokenResponse);
291292
return Promise.resolve(tokenResponse);
292293
}
293294
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {Schema} from '../models/scim2-schema';
20+
import AsgardeoAPIError from '../errors/AsgardeoAPIError';
21+
22+
/**
23+
* Configuration for the getSchemas request
24+
*/
25+
export interface GetSchemasConfig extends Omit<RequestInit, 'method'> {
26+
/**
27+
* The absolute API endpoint.
28+
*/
29+
url?: string;
30+
/**
31+
* The base path of the API endpoint.
32+
*/
33+
baseUrl?: string;
34+
/**
35+
* Optional custom fetcher function.
36+
* If not provided, native fetch will be used
37+
*/
38+
fetcher?: (url: string, config: RequestInit) => Promise<Response>;
39+
}
40+
41+
/**
42+
* Retrieves the SCIM2 schemas from the specified endpoint.
43+
*
44+
* @param config - Request configuration object.
45+
* @returns A promise that resolves with the SCIM2 schemas information.
46+
* @example
47+
* ```typescript
48+
* // Using default fetch
49+
* try {
50+
* const schemas = await getSchemas({
51+
* url: "https://api.asgardeo.io/t/<ORGANIZATION>/scim2/Schemas",
52+
* });
53+
* console.log(schemas);
54+
* } catch (error) {
55+
* if (error instanceof AsgardeoAPIError) {
56+
* console.error('Failed to get schemas:', error.message);
57+
* }
58+
* }
59+
* ```
60+
*
61+
* @example
62+
* ```typescript
63+
* // Using custom fetcher (e.g., axios-based httpClient)
64+
* try {
65+
* const schemas = await getSchemas({
66+
* url: "https://api.asgardeo.io/t/<ORGANIZATION>/scim2/Schemas",
67+
* fetcher: async (url, config) => {
68+
* const response = await httpClient({
69+
* url,
70+
* method: config.method,
71+
* headers: config.headers,
72+
* ...config
73+
* });
74+
* // Convert axios-like response to fetch-like Response
75+
* return {
76+
* ok: response.status >= 200 && response.status < 300,
77+
* status: response.status,
78+
* statusText: response.statusText,
79+
* json: () => Promise.resolve(response.data),
80+
* text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data))
81+
* } as Response;
82+
* }
83+
* });
84+
* console.log(schemas);
85+
* } catch (error) {
86+
* if (error instanceof AsgardeoAPIError) {
87+
* console.error('Failed to get schemas:', error.message);
88+
* }
89+
* }
90+
* ```
91+
*/
92+
const getSchemas = async ({url, baseUrl, fetcher, ...requestConfig}: GetSchemasConfig): Promise<Schema[]> => {
93+
try {
94+
new URL(url ?? baseUrl);
95+
} catch (error) {
96+
throw new AsgardeoAPIError(
97+
`Invalid URL provided. ${error?.toString()}`,
98+
'getSchemas-ValidationError-001',
99+
'javascript',
100+
400,
101+
'The provided `url` or `baseUrl` path does not adhere to the URL schema.',
102+
);
103+
}
104+
105+
const fetchFn = fetcher || fetch;
106+
const resolvedUrl: string = url ?? `${baseUrl}/scim2/Schemas`;
107+
108+
const requestInit: RequestInit = {
109+
method: 'GET',
110+
headers: {
111+
'Content-Type': 'application/scim+json',
112+
Accept: 'application/json',
113+
...requestConfig.headers,
114+
},
115+
...requestConfig,
116+
};
117+
118+
try {
119+
const response: Response = await fetchFn(resolvedUrl, requestInit);
120+
121+
if (!response?.ok) {
122+
const errorText = await response.text();
123+
124+
throw new AsgardeoAPIError(
125+
`Failed to fetch SCIM2 schemas: ${errorText}`,
126+
'getSchemas-ResponseError-001',
127+
'javascript',
128+
response.status,
129+
response.statusText,
130+
);
131+
}
132+
133+
const responseData = await response.json();
134+
135+
// Handle both array response and Resources array response
136+
if (Array.isArray(responseData)) {
137+
return responseData as Schema[];
138+
} else if (
139+
responseData &&
140+
typeof responseData === 'object' &&
141+
'Resources' in responseData &&
142+
Array.isArray(responseData.Resources)
143+
) {
144+
return responseData.Resources as Schema[];
145+
} else {
146+
return [responseData] as Schema[];
147+
}
148+
} catch (error) {
149+
if (error instanceof AsgardeoAPIError) {
150+
throw error;
151+
}
152+
153+
throw new AsgardeoAPIError(
154+
`Network or parsing error: ${error instanceof Error ? error.message : 'Unknown error'}`,
155+
'getSchemas-NetworkError-001',
156+
'javascript',
157+
0,
158+
'Network Error',
159+
);
160+
}
161+
};
162+
163+
export default getSchemas;

packages/javascript/src/api/scim2/__tests__/getMeProfile.test.ts

Whitespace-only changes.

packages/javascript/src/api/scim2/getMeProfile.ts

Whitespace-only changes.

packages/javascript/src/api/scim2/index.ts

Whitespace-only changes.

packages/javascript/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export {default as executeEmbeddedSignInFlow} from './api/executeEmbeddedSignInF
2626
export {default as executeEmbeddedSignUpFlow} from './api/executeEmbeddedSignUpFlow';
2727
export {default as getUserInfo} from './api/getUserInfo';
2828
export {default as getScim2Me, GetScim2MeConfig} from './api/getScim2Me';
29+
export {default as getSchemas, GetSchemasConfig} from './api/getSchemas';
2930

3031
export {default as ApplicationNativeAuthenticationConstants} from './constants/ApplicationNativeAuthenticationConstants';
3132
export {default as TokenConstants} from './constants/TokenConstants';

packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ const getAuthorizeRequestUrlParams = (
5555
options: {
5656
redirectUri: string;
5757
clientId: string;
58-
clientSecret?: string;
5958
scopes?: string;
6059
responseMode?: string;
6160
codeChallenge?: string;
@@ -79,10 +78,6 @@ const getAuthorizeRequestUrlParams = (
7978
authorizeRequestParams.set('response_mode', responseMode as string);
8079
}
8180

82-
if (clientSecret) {
83-
authorizeRequestParams.set('client_secret', clientSecret as string);
84-
}
85-
8681
const pkceKey: string = pkceOptions?.key;
8782

8883
if (codeChallenge) {

packages/nextjs/src/AsgardeoNextClient.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import {
3333
executeEmbeddedSignInFlow,
3434
EmbeddedFlowExecuteRequestConfig,
3535
ExtendedAuthorizeRequestUrlParams,
36+
generateUserProfile,
37+
flattenUserSchema,
38+
getScim2Me,
3639
} from '@asgardeo/node';
3740
import {NextRequest, NextResponse} from 'next/server';
3841
import {AsgardeoNextConfig} from './models/config';
@@ -52,7 +55,7 @@ const removeTrailingSlash = (path: string): string => (path.endsWith('/') ? path
5255
class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> extends AsgardeoNodeClient<T> {
5356
private static instance: AsgardeoNextClient<any>;
5457
private asgardeo: LegacyAsgardeoNodeClient<T>;
55-
private isInitialized: boolean = false;
58+
public isInitialized: boolean = false;
5659

5760
private constructor() {
5861
super();
@@ -123,7 +126,18 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
123126
override async getUser(userId?: string): Promise<User> {
124127
await this.ensureInitialized();
125128
const resolvedSessionId: string = userId || ((await getSessionId()) as string);
126-
return this.asgardeo.getUser(resolvedSessionId);
129+
130+
try {
131+
const configData = await this.asgardeo.getConfigData();
132+
const baseUrl = configData?.baseUrl;
133+
134+
const profile = await getScim2Me({baseUrl});
135+
const schemas = await getSchemas({url: `${baseUrl}/scim2/Schemas`});
136+
137+
return generateUserProfile(profile, flattenUserSchema(schemas));
138+
} catch (error) {
139+
return this.asgardeo.getUser(resolvedSessionId);
140+
}
127141
}
128142

129143
override async getOrganizations(): Promise<Organization[]> {
@@ -176,10 +190,12 @@ class AsgardeoNextClient<T extends AsgardeoNextConfig = AsgardeoNextConfig> exte
176190
const defaultSignInUrl: URL = new URL(
177191
await this.getAuthorizeRequestUrl({
178192
response_mode: 'direct',
179-
...(arg3 ?? {}),
193+
client_secret: '{{clientSecret}}',
180194
}),
181195
);
182196

197+
console.log('[AsgardeoNextClient] Redirecting to sign-in URL:', defaultSignInUrl);
198+
183199
return initializeEmbeddedSignInFlow({
184200
url: `${defaultSignInUrl.origin}${defaultSignInUrl.pathname}`,
185201
payload: Object.fromEntries(defaultSignInUrl.searchParams.entries()),

0 commit comments

Comments
 (0)