UNPKG

@asgardeo/nextjs

Version:

Next.js implementation of Asgardeo JavaScript SDK.

353 lines 15.8 kB
/** * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { AsgardeoNodeClient, AsgardeoRuntimeError, LegacyAsgardeoNodeClient, initializeEmbeddedSignInFlow, executeEmbeddedSignInFlow, generateUserProfile, flattenUserSchema, getScim2Me, getSchemas, generateFlattenedUserProfile, updateMeProfile, executeEmbeddedSignUpFlow, getMeOrganizations, createOrganization, getOrganization, deriveOrganizationHandleFromBaseUrl, getAllOrganizations, extractUserClaimsFromIdToken, } from '@asgardeo/node'; import getSessionId from './server/actions/getSessionId'; import decorateConfigWithNextEnv from './utils/decorateConfigWithNextEnv'; import getClientOrigin from './server/actions/getClientOrigin'; const removeTrailingSlash = (path) => (path.endsWith('/') ? path.slice(0, -1) : path); /** * Client for mplementing Asgardeo in Next.js applications. * This class provides the core functionality for managing user authentication and sessions. * * This class is implemented as a singleton to ensure a single instance across the application. * * @typeParam T - Configuration type that extends AsgardeoNextConfig. */ class AsgardeoNextClient extends AsgardeoNodeClient { static instance; asgardeo; isInitialized = false; constructor() { super(); this.asgardeo = new LegacyAsgardeoNodeClient(); } /** * Get the singleton instance of AsgardeoNextClient */ static getInstance() { if (!AsgardeoNextClient.instance) { AsgardeoNextClient.instance = new AsgardeoNextClient(); } return AsgardeoNextClient.instance; } /** * Ensures the client is initialized before using it. * Throws an error if the client is not initialized. */ async ensureInitialized() { if (!this.isInitialized) { throw new Error('[AsgardeoNextClient] Client is not initialized. Make sure you have wrapped your app with AsgardeoProvider and provided the required configuration (baseUrl, clientId, etc.).'); } } async initialize(config, storage) { if (this.isInitialized) { return Promise.resolve(true); } const { baseUrl, organizationHandle, clientId, clientSecret, signInUrl, afterSignInUrl, afterSignOutUrl, signUpUrl, ...rest } = decorateConfigWithNextEnv(config); this.isInitialized = true; let resolvedOrganizationHandle = organizationHandle; if (!resolvedOrganizationHandle) { resolvedOrganizationHandle = deriveOrganizationHandleFromBaseUrl(baseUrl); } const origin = await getClientOrigin(); return this.asgardeo.initialize({ organizationHandle: resolvedOrganizationHandle, baseUrl, clientId, clientSecret, signInUrl, signUpUrl, afterSignInUrl: afterSignInUrl ?? origin, afterSignOutUrl: afterSignOutUrl ?? origin, enablePKCE: false, ...rest, }, storage); } async getUser(userId) { await this.ensureInitialized(); const resolvedSessionId = userId || (await getSessionId()); try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; const profile = await getScim2Me({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); const schemas = await getSchemas({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); return generateUserProfile(profile, flattenUserSchema(schemas)); } catch (error) { return this.asgardeo.getUser(resolvedSessionId); } } async getUserProfile(userId) { await this.ensureInitialized(); try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; const profile = await getScim2Me({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); const schemas = await getSchemas({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); const processedSchemas = flattenUserSchema(schemas); const output = { schemas: processedSchemas, flattenedProfile: generateFlattenedUserProfile(profile, processedSchemas), profile, }; return output; } catch (error) { return { schemas: [], flattenedProfile: extractUserClaimsFromIdToken(await this.asgardeo.getDecodedIdToken(userId)), profile: extractUserClaimsFromIdToken(await this.asgardeo.getDecodedIdToken(userId)), }; } } async updateUserProfile(payload, userId) { await this.ensureInitialized(); try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return await updateMeProfile({ baseUrl, payload, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); } catch (error) { throw new AsgardeoRuntimeError(`Failed to update user profile: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-UpdateProfileError-001', 'react', 'An error occurred while updating the user profile. Please check your configuration and network connection.'); } } async createOrganization(payload, userId) { try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return await createOrganization({ payload, baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); } catch (error) { throw new AsgardeoRuntimeError(`Failed to create organization: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoReactClient-createOrganization-RuntimeError-001', 'nextjs', 'An error occurred while creating the organization. Please check your configuration and network connection.'); } } async getOrganization(organizationId, userId) { try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return await getOrganization({ baseUrl, organizationId, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); } catch (error) { throw new AsgardeoRuntimeError(`Failed to fetch the organization details of ${organizationId}: ${String(error)}`, 'AsgardeoReactClient-getOrganization-RuntimeError-001', 'nextjs', `An error occurred while fetching the organization with the id: ${organizationId}.`); } } async getMyOrganizations(options, userId) { try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return await getMeOrganizations({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); } catch (error) { throw new AsgardeoRuntimeError(`Failed to fetch the user's associated organizations: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-getMyOrganizations-RuntimeError-001', 'nextjs', 'An error occurred while fetching associated organizations of the signed-in user.'); } } async getAllOrganizations(options, userId) { try { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return getAllOrganizations({ baseUrl, headers: { Authorization: `Bearer ${await this.getAccessToken(userId)}`, }, }); } catch (error) { throw new AsgardeoRuntimeError(`Failed to fetch all organizations: ${error instanceof Error ? error.message : String(error)}`, 'AsgardeoNextClient-getAllOrganizations-RuntimeError-001', 'nextjs', 'An error occurred while fetching all the organizations associated with the user.'); } } async getCurrentOrganization(userId) { const idToken = await this.asgardeo.getDecodedIdToken(userId); return { orgHandle: idToken?.org_handle, name: idToken?.org_name, id: idToken?.org_id, }; } async switchOrganization(organization, userId) { try { const configData = await this.asgardeo.getConfigData(); const scopes = configData?.scopes; if (!organization.id) { throw new AsgardeoRuntimeError('Organization ID is required for switching organizations', 'AsgardeoNextClient-switchOrganization-ValidationError-001', 'nextjs', 'The organization object must contain a valid ID to perform the organization switch.'); } const exchangeConfig = { attachToken: false, data: { client_id: '{{clientId}}', client_secret: '{{clientSecret}}', grant_type: 'organization_switch', scope: '{{scopes}}', switching_organization: organization.id, token: '{{accessToken}}', }, id: 'organization-switch', returnsSession: true, signInRequired: true, }; return await this.asgardeo.exchangeToken(exchangeConfig, userId); } catch (error) { throw new AsgardeoRuntimeError(`Failed to switch organization: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, 'AsgardeoReactClient-RuntimeError-003', 'nextjs', 'An error occurred while switching to the specified organization. Please try again.'); } } isLoading() { return false; } isSignedIn(sessionId) { return this.asgardeo.isSignedIn(sessionId); } /** * Gets the access token from the session cookie if no sessionId is provided, * otherwise falls back to legacy client method. */ async getAccessToken(sessionId) { const { default: getAccessToken } = await import('./server/actions/getAccessToken'); const token = await getAccessToken(); if (typeof token !== 'string' || !token) { throw new Error('Access token not found'); throw new AsgardeoRuntimeError('Failed to get access token.', 'AsgardeoNextClient-getAccessToken-RuntimeError-003', 'nextjs', 'An error occurred while obtaining the access token. Please check your configuration and network connection.'); } return token; } /** * Get the decoded ID token for a session */ async getDecodedIdToken(sessionId, idToken) { await this.ensureInitialized(); return this.asgardeo.getDecodedIdToken(sessionId, idToken); } getConfiguration() { return this.asgardeo.getConfigData(); } async signIn(...args) { const arg1 = args[0]; const arg2 = args[1]; const arg3 = args[2]; const arg4 = args[3]; if (typeof arg1 === 'object' && 'flowId' in arg1) { if (arg1.flowId === '') { const defaultSignInUrl = new URL(await this.getAuthorizeRequestUrl({ response_mode: 'direct', client_secret: '{{clientSecret}}', })); return initializeEmbeddedSignInFlow({ url: `${defaultSignInUrl.origin}${defaultSignInUrl.pathname}`, payload: Object.fromEntries(defaultSignInUrl.searchParams.entries()), }); } return executeEmbeddedSignInFlow({ payload: arg1, url: arg2.url, }); } return this.asgardeo.signIn(arg4, arg3, arg1?.code, arg1?.session_state, arg1?.state, arg1); } async signOut(...args) { if (args[1] && typeof args[1] !== 'string') { throw new Error('The second argument must be a string.'); } const resolvedSessionId = args[1] || (await getSessionId()); return Promise.resolve(await this.asgardeo.signOut(resolvedSessionId)); } async signUp(...args) { if (args.length === 0) { throw new AsgardeoRuntimeError('No arguments provided for signUp method.', 'AsgardeoNextClient-ValidationError-001', 'nextjs', 'The signUp method requires at least one argument, either a SignUpOptions object or an EmbeddedFlowExecuteRequestPayload.'); } const firstArg = args[0]; if (typeof firstArg === 'object' && 'flowType' in firstArg) { const configData = await this.asgardeo.getConfigData(); const baseUrl = configData?.baseUrl; return executeEmbeddedSignUpFlow({ baseUrl, payload: firstArg, }); } throw new AsgardeoRuntimeError('Not implemented', 'AsgardeoNextClient-ValidationError-002', 'nextjs', 'The signUp method with SignUpOptions is not implemented in the Next.js client.'); } signInSilently(options) { throw new AsgardeoRuntimeError('Not implemented', 'AsgardeoNextClient-signInSilently-NotImplementedError-001', 'nextjs', 'The signInSilently method is not implemented in the Next.js client.'); } /** * Gets the sign-in URL for authentication. * Ensures the client is initialized before making the call. * * @param customParams - Custom parameters to include in the sign-in URL. * @param userId - The user ID * @returns Promise that resolves to the sign-in URL */ async getAuthorizeRequestUrl(customParams, userId) { await this.ensureInitialized(); return this.asgardeo.getSignInUrl(customParams, userId); } /** * Gets the storage manager from the underlying Asgardeo client. * Ensures the client is initialized before making the call. * * @returns Promise that resolves to the storage manager */ async getStorageManager() { await this.ensureInitialized(); return this.asgardeo.getStorageManager(); } } export default AsgardeoNextClient; //# sourceMappingURL=AsgardeoNextClient.js.map