From 370a4242874c526e44db670a5fb246d14f31b228 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Tue, 10 Mar 2026 15:10:32 +0530 Subject: [PATCH 1/7] Refactor AsgardeoNextClient to support multiple instances and update session management utilities for instance handling - Step 1, 2, 3 --- packages/nextjs/src/AsgardeoNextClient.ts | 41 +- packages/nextjs/src/utils/SessionManager.ts | 432 ++++++++++---------- packages/nextjs/src/utils/sessionUtils.ts | 204 ++++----- 3 files changed, 353 insertions(+), 324 deletions(-) diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 1f7e16277..2ef15026f 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -66,31 +66,54 @@ import decorateConfigWithNextEnv from './utils/decorateConfigWithNextEnv'; * 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. + * This class is implemented as a multiton to support multiple independent instances across the application. * * @typeParam T - Configuration type that extends AsgardeoNextConfig. */ class AsgardeoNextClient extends AsgardeoNodeClient { - private static instance: AsgardeoNextClient; + private static instances: Map> = new Map(); + + private instanceId: number; private asgardeo: LegacyAsgardeoNodeClient; public isInitialized: boolean = false; - private constructor() { + private constructor(instanceId: number = 0) { super(); - + this.instanceId = instanceId; this.asgardeo = new LegacyAsgardeoNodeClient(); } /** - * Get the singleton instance of AsgardeoNextClient + * Get the instance of AsgardeoNextClient for the given instanceId. */ - public static getInstance(): AsgardeoNextClient { - if (!AsgardeoNextClient.instance) { - AsgardeoNextClient.instance = new AsgardeoNextClient(); + public static getInstance(instanceId: number = 0): AsgardeoNextClient { + if (!AsgardeoNextClient.instances.has(instanceId)) { + AsgardeoNextClient.instances.set(instanceId, new AsgardeoNextClient(instanceId)); } - return AsgardeoNextClient.instance as AsgardeoNextClient; + return AsgardeoNextClient.instances.get(instanceId) as AsgardeoNextClient; + } + + /** + * Returns the instanceId of this client instance. + */ + public getInstanceId(): number { + return this.instanceId; + } + + /** + * Returns whether an instance with the given instanceId exists. + */ + public static hasInstance(instanceId: number = 0): boolean { + return AsgardeoNextClient.instances.has(instanceId); + } + + /** + * Destroys the instance with the given instanceId. + */ + public static destroyInstance(instanceId: number = 0): boolean { + return AsgardeoNextClient.instances.delete(instanceId); } /** diff --git a/packages/nextjs/src/utils/SessionManager.ts b/packages/nextjs/src/utils/SessionManager.ts index a83cceb09..9f825071a 100644 --- a/packages/nextjs/src/utils/SessionManager.ts +++ b/packages/nextjs/src/utils/SessionManager.ts @@ -1,213 +1,219 @@ -/** - * 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 {AsgardeoRuntimeError, CookieConfig} from '@asgardeo/node'; -import {SignJWT, jwtVerify, JWTPayload} from 'jose'; - -/** - * Session token payload interface - */ -export interface SessionTokenPayload extends JWTPayload { - /** Expiration timestamp */ - exp: number; - /** Issued at timestamp */ - iat: number; - /** Organization ID if applicable */ - organizationId?: string; - /** OAuth scopes */ - scopes: string[]; - /** Session ID */ - sessionId: string; - /** User ID */ - sub: string; -} - -/** - * Session management utility class for JWT-based session cookies - */ -class SessionManager { - private static readonly DEFAULT_EXPIRY_SECONDS: number = 3600; - - /** - * Get the signing secret from environment variable - * Throws error in production if not set - */ - private static getSecret(): Uint8Array { - const secret: string | undefined = process.env['ASGARDEO_SECRET']; - - if (!secret) { - if (process.env['NODE_ENV'] === 'production') { - throw new AsgardeoRuntimeError( - 'ASGARDEO_SECRET environment variable is required in production', - 'session-secret-required', - 'nextjs', - 'Set the ASGARDEO_SECRET environment variable with a secure random string', - ); - } - // Use a default secret for development (not secure) - // eslint-disable-next-line no-console - console.warn('Using default secret for development. Set ASGARDEO_SECRET for production!'); - return new TextEncoder().encode('development-secret-not-for-production'); - } - - return new TextEncoder().encode(secret); - } - - /** - * Create a temporary session cookie for login initiation - */ - static async createTempSession(sessionId: string): Promise { - const secret: Uint8Array = this.getSecret(); - - const jwt: string = await new SignJWT({ - sessionId, - type: 'temp', - }) - .setProtectedHeader({alg: 'HS256'}) - .setIssuedAt() - .setExpirationTime('15m') - .sign(secret); - - return jwt; - } - - /** - * Create a session cookie with user information - */ - static async createSessionToken( - accessToken: string, - userId: string, - sessionId: string, - scopes: string, - organizationId?: string, - expirySeconds: number = this.DEFAULT_EXPIRY_SECONDS, - ): Promise { - const secret: Uint8Array = this.getSecret(); - - const jwt: string = await new SignJWT({ - accessToken, - organizationId, - scopes, - sessionId, - type: 'session', - } as Omit) - .setProtectedHeader({alg: 'HS256'}) - .setSubject(userId) - .setIssuedAt() - .setExpirationTime(Date.now() / 1000 + expirySeconds) - .sign(secret); - - return jwt; - } - - /** - * Verify and decode a session token - */ - static async verifySessionToken(token: string): Promise { - try { - const secret: Uint8Array = this.getSecret(); - const {payload} = await jwtVerify(token, secret); - - return payload as SessionTokenPayload; - } catch (error) { - throw new AsgardeoRuntimeError( - `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`, - 'invalid-session-token', - 'nextjs', - 'Session token verification failed', - ); - } - } - - /** - * Verify and decode a temporary session token - */ - static async verifyTempSession(token: string): Promise<{sessionId: string}> { - try { - const secret: Uint8Array = this.getSecret(); - const {payload} = await jwtVerify(token, secret); - - if (payload['type'] !== 'temp') { - throw new Error('Invalid token type'); - } - - return {sessionId: payload['sessionId'] as string}; - } catch (error) { - throw new AsgardeoRuntimeError( - `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`, - 'invalid-temp-session-token', - 'nextjs', - 'Temporary session token verification failed', - ); - } - } - - /** - * Get session cookie options - */ - static getSessionCookieOptions(): { - httpOnly: boolean; - maxAge: number; - path: string; - sameSite: 'lax'; - secure: boolean; - } { - return { - httpOnly: true, - maxAge: this.DEFAULT_EXPIRY_SECONDS, - path: '/', - sameSite: 'lax' as const, - secure: process.env['NODE_ENV'] === 'production', - }; - } - - /** - * Get temporary session cookie options - */ - static getTempSessionCookieOptions(): { - httpOnly: boolean; - maxAge: number; - path: string; - sameSite: 'lax'; - secure: boolean; - } { - return { - httpOnly: true, - maxAge: 15 * 60, - path: '/', - sameSite: 'lax' as const, - secure: process.env['NODE_ENV'] === 'production', - }; - } - - /** - * Get session cookie name - */ - static getSessionCookieName(): string { - return CookieConfig.SESSION_COOKIE_NAME; - } - - /** - * Get temporary session cookie name - */ - static getTempSessionCookieName(): string { - return CookieConfig.TEMP_SESSION_COOKIE_NAME; - } -} - -export default SessionManager; +/** + * 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 {AsgardeoRuntimeError, CookieConfig} from '@asgardeo/node'; +import {SignJWT, jwtVerify, JWTPayload} from 'jose'; + +/** + * Session token payload interface + */ +export interface SessionTokenPayload extends JWTPayload { + /** Expiration timestamp */ + exp: number; + /** Issued at timestamp */ + iat: number; + /** Organization ID if applicable */ + organizationId?: string; + /** OAuth scopes */ + scopes: string[]; + /** Session ID */ + sessionId: string; + /** User ID */ + sub: string; +} + +/** + * Session management utility class for JWT-based session cookies + */ +class SessionManager { + private static readonly DEFAULT_EXPIRY_SECONDS: number = 3600; + + /** + * Get the signing secret from environment variable + * Throws error in production if not set + */ + private static getSecret(): Uint8Array { + const secret: string | undefined = process.env['ASGARDEO_SECRET']; + + if (!secret) { + if (process.env['NODE_ENV'] === 'production') { + throw new AsgardeoRuntimeError( + 'ASGARDEO_SECRET environment variable is required in production', + 'session-secret-required', + 'nextjs', + 'Set the ASGARDEO_SECRET environment variable with a secure random string', + ); + } + // Use a default secret for development (not secure) + // eslint-disable-next-line no-console + console.warn('Using default secret for development. Set ASGARDEO_SECRET for production!'); + return new TextEncoder().encode('development-secret-not-for-production'); + } + + return new TextEncoder().encode(secret); + } + + /** + * Create a temporary session cookie for login initiation + */ + static async createTempSession(sessionId: string): Promise { + const secret: Uint8Array = this.getSecret(); + + const jwt: string = await new SignJWT({ + sessionId, + type: 'temp', + }) + .setProtectedHeader({alg: 'HS256'}) + .setIssuedAt() + .setExpirationTime('15m') + .sign(secret); + + return jwt; + } + + /** + * Create a session cookie with user information + */ + static async createSessionToken( + accessToken: string, + userId: string, + sessionId: string, + scopes: string, + organizationId?: string, + expirySeconds: number = this.DEFAULT_EXPIRY_SECONDS, + ): Promise { + const secret: Uint8Array = this.getSecret(); + + const jwt: string = await new SignJWT({ + accessToken, + organizationId, + scopes, + sessionId, + type: 'session', + } as Omit) + .setProtectedHeader({alg: 'HS256'}) + .setSubject(userId) + .setIssuedAt() + .setExpirationTime(Date.now() / 1000 + expirySeconds) + .sign(secret); + + return jwt; + } + + /** + * Verify and decode a session token + */ + static async verifySessionToken(token: string): Promise { + try { + const secret: Uint8Array = this.getSecret(); + const {payload} = await jwtVerify(token, secret); + + return payload as SessionTokenPayload; + } catch (error) { + throw new AsgardeoRuntimeError( + `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'invalid-session-token', + 'nextjs', + 'Session token verification failed', + ); + } + } + + /** + * Verify and decode a temporary session token + */ + static async verifyTempSession(token: string): Promise<{sessionId: string}> { + try { + const secret: Uint8Array = this.getSecret(); + const {payload} = await jwtVerify(token, secret); + + if (payload['type'] !== 'temp') { + throw new Error('Invalid token type'); + } + + return {sessionId: payload['sessionId'] as string}; + } catch (error) { + throw new AsgardeoRuntimeError( + `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'invalid-temp-session-token', + 'nextjs', + 'Temporary session token verification failed', + ); + } + } + + /** + * Get session cookie options + */ + static getSessionCookieOptions(): { + httpOnly: boolean; + maxAge: number; + path: string; + sameSite: 'lax'; + secure: boolean; + } { + return { + httpOnly: true, + maxAge: this.DEFAULT_EXPIRY_SECONDS, + path: '/', + sameSite: 'lax' as const, + secure: process.env['NODE_ENV'] === 'production', + }; + } + + /** + * Get temporary session cookie options + */ + static getTempSessionCookieOptions(): { + httpOnly: boolean; + maxAge: number; + path: string; + sameSite: 'lax'; + secure: boolean; + } { + return { + httpOnly: true, + maxAge: 15 * 60, + path: '/', + sameSite: 'lax' as const, + secure: process.env['NODE_ENV'] === 'production', + }; + } + + /** + * Get session cookie name + */ + static getSessionCookieName(instanceId: number = 0): string { + if (instanceId === 0) { + return CookieConfig.SESSION_COOKIE_NAME; + } + return `${CookieConfig.SESSION_COOKIE_NAME}.${instanceId}`; + } + + /** + * Get temporary session cookie name + */ + static getTempSessionCookieName(instanceId: number = 0): string { + if (instanceId === 0) { + return CookieConfig.TEMP_SESSION_COOKIE_NAME; + } + return `${CookieConfig.TEMP_SESSION_COOKIE_NAME}.${instanceId}`; + } +} + +export default SessionManager; diff --git a/packages/nextjs/src/utils/sessionUtils.ts b/packages/nextjs/src/utils/sessionUtils.ts index 2aa572173..8d7ea49bf 100644 --- a/packages/nextjs/src/utils/sessionUtils.ts +++ b/packages/nextjs/src/utils/sessionUtils.ts @@ -1,102 +1,102 @@ -/** - * 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 {NextRequest} from 'next/server'; -import SessionManager, {SessionTokenPayload} from './SessionManager'; - -/** - * Checks if a request has a valid session cookie (JWT). - * This verifies the JWT signature and expiration. - * - * @param request - The Next.js request object - * @returns True if a valid session exists, false otherwise - */ -export const hasValidSession = async (request: NextRequest): Promise => { - try { - const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName())?.value; - if (!sessionToken) { - return false; - } - - await SessionManager.verifySessionToken(sessionToken); - return true; - } catch { - return false; - } -}; - -/** - * Gets the session payload from the request cookies. - * This includes user ID, session ID, and scopes. - * - * @param request - The Next.js request object - * @returns The session payload if valid, undefined otherwise - */ -export const getSessionFromRequest = async (request: NextRequest): Promise => { - try { - const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName())?.value; - if (!sessionToken) { - return undefined; - } - - return await SessionManager.verifySessionToken(sessionToken); - } catch { - return undefined; - } -}; - -/** - * Gets the session ID from the request cookies (legacy support). - * First tries to get from JWT session, then falls back to legacy session ID cookie. - * - * @param request - The Next.js request object - * @returns The session ID if it exists, undefined otherwise - */ -export const getSessionIdFromRequest = async (request: NextRequest): Promise => { - try { - const sessionPayload: SessionTokenPayload | undefined = await getSessionFromRequest(request); - - if (sessionPayload) { - return sessionPayload.sessionId; - } - - return await Promise.resolve(undefined); - } catch { - return Promise.resolve(undefined); - } -}; - -/** - * Gets the temporary session ID from request cookies. - * - * @param request - The Next.js request object - * @returns The temporary session ID if valid, undefined otherwise - */ -export const getTempSessionFromRequest = async (request: NextRequest): Promise => { - try { - const tempToken: string | undefined = request.cookies.get(SessionManager.getTempSessionCookieName())?.value; - if (!tempToken) { - return undefined; - } - - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempToken); - return tempSession.sessionId; - } catch { - return undefined; - } -}; +/** + * 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 {NextRequest} from 'next/server'; +import SessionManager, {SessionTokenPayload} from './SessionManager'; + +/** + * Checks if a request has a valid session cookie (JWT). + * This verifies the JWT signature and expiration. + * + * @param request - The Next.js request object + * @returns True if a valid session exists, false otherwise + */ +export const hasValidSession = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return false; + } + + await SessionManager.verifySessionToken(sessionToken); + return true; + } catch { + return false; + } +}; + +/** + * Gets the session payload from the request cookies. + * This includes user ID, session ID, and scopes. + * + * @param request - The Next.js request object + * @returns The session payload if valid, undefined otherwise + */ +export const getSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return undefined; + } + + return await SessionManager.verifySessionToken(sessionToken); + } catch { + return undefined; + } +}; + +/** + * Gets the session ID from the request cookies (legacy support). + * First tries to get from JWT session, then falls back to legacy session ID cookie. + * + * @param request - The Next.js request object + * @returns The session ID if it exists, undefined otherwise + */ +export const getSessionIdFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionPayload: SessionTokenPayload | undefined = await getSessionFromRequest(request, instanceId); + + if (sessionPayload) { + return sessionPayload.sessionId; + } + + return await Promise.resolve(undefined); + } catch { + return Promise.resolve(undefined); + } +}; + +/** + * Gets the temporary session ID from request cookies. + * + * @param request - The Next.js request object + * @returns The temporary session ID if valid, undefined otherwise + */ +export const getTempSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const tempToken: string | undefined = request.cookies.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + if (!tempToken) { + return undefined; + } + + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempToken); + return tempSession.sessionId; + } catch { + return undefined; + } +}; From b60bc0ae6ecd57c279825af27c94cad2b9eccdf1 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Tue, 10 Mar 2026 15:11:05 +0530 Subject: [PATCH 2/7] Refactor server actions to support instance ID parameter - Step 4 - Updated `isSignedIn` to accept an optional instance ID for better session management. - Modified `signInAction`, `signOutAction`, `signUpAction`, `switchOrganization`, and `updateUserProfileAction` to include instance ID handling. - Ensured that session cookies are managed per instance to support multi-instance scenarios. - Improved error handling and logging across actions for better debugging. --- .../src/server/actions/createOrganization.ts | 84 ++--- .../src/server/actions/getAccessToken.ts | 96 +++--- .../src/server/actions/getAllOrganizations.ts | 91 +++--- .../actions/getCurrentOrganizationAction.ts | 99 +++--- .../src/server/actions/getMyOrganizations.ts | 164 +++++----- .../server/actions/getOrganizationAction.ts | 101 +++--- .../nextjs/src/server/actions/getSessionId.ts | 98 +++--- .../src/server/actions/getSessionPayload.ts | 92 +++--- .../src/server/actions/getUserAction.ts | 81 ++--- .../server/actions/getUserProfileAction.ts | 101 +++--- .../actions/handleOAuthCallbackAction.ts | 297 ++++++++--------- .../nextjs/src/server/actions/isSignedIn.ts | 142 ++++---- .../nextjs/src/server/actions/signInAction.ts | 305 +++++++++--------- .../src/server/actions/signOutAction.ts | 164 +++++----- .../nextjs/src/server/actions/signUpAction.ts | 137 ++++---- .../src/server/actions/switchOrganization.ts | 165 +++++----- .../server/actions/updateUserProfileAction.ts | 95 +++--- 17 files changed, 1161 insertions(+), 1151 deletions(-) diff --git a/packages/nextjs/src/server/actions/createOrganization.ts b/packages/nextjs/src/server/actions/createOrganization.ts index 49dc852b4..5249c3fcf 100644 --- a/packages/nextjs/src/server/actions/createOrganization.ts +++ b/packages/nextjs/src/server/actions/createOrganization.ts @@ -1,42 +1,42 @@ -/** - * 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. - */ - -'use server'; - -import {CreateOrganizationPayload, Organization, AsgardeoAPIError} from '@asgardeo/node'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const createOrganization = async (payload: CreateOrganizationPayload, sessionId: string): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - return await client.createOrganization(payload, sessionId ?? ((await getSessionId()) as string)); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to create the organization: ${error instanceof Error ? error.message : String(error)}`, - 'createOrganization-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default createOrganization; +/** + * 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. + */ + +'use server'; + +import {CreateOrganizationPayload, Organization, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const createOrganization = async (payload: CreateOrganizationPayload, sessionId: string, instanceId: number = 0): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + return await client.createOrganization(payload, sessionId ?? ((await getSessionId(instanceId)) as string)); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to create the organization: ${error instanceof Error ? error.message : String(error)}`, + 'createOrganization-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default createOrganization; diff --git a/packages/nextjs/src/server/actions/getAccessToken.ts b/packages/nextjs/src/server/actions/getAccessToken.ts index 9d7a351fe..45712c69b 100644 --- a/packages/nextjs/src/server/actions/getAccessToken.ts +++ b/packages/nextjs/src/server/actions/getAccessToken.ts @@ -1,48 +1,48 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the access token from the session cookie. - * - * @returns The access token if it exists, undefined otherwise - */ -const getAccessToken = async (): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName())?.value; - - if (sessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); - - return sessionPayload['accessToken'] as string; - } catch (error) { - return undefined; - } - } - - return undefined; -}; - -export default getAccessToken; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the access token from the session cookie. + * + * @returns The access token if it exists, undefined otherwise + */ +const getAccessToken = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (sessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); + + return sessionPayload['accessToken'] as string; + } catch (error) { + return undefined; + } + } + + return undefined; +}; + +export default getAccessToken; diff --git a/packages/nextjs/src/server/actions/getAllOrganizations.ts b/packages/nextjs/src/server/actions/getAllOrganizations.ts index 4fcbef8d3..333fb16eb 100644 --- a/packages/nextjs/src/server/actions/getAllOrganizations.ts +++ b/packages/nextjs/src/server/actions/getAllOrganizations.ts @@ -1,45 +1,46 @@ -/** - * 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. - */ - -'use server'; - -import {AllOrganizationsApiResponse, AsgardeoAPIError} from '@asgardeo/node'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get organizations. - */ -const getAllOrganizations = async ( - options?: any, - sessionId?: string | undefined, -): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - return await client.getAllOrganizations(options, sessionId ?? ((await getSessionId()) as string)); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to get all the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, - 'getAllOrganizations-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default getAllOrganizations; +/** + * 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. + */ + +'use server'; + +import {AllOrganizationsApiResponse, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get organizations. + */ +const getAllOrganizations = async ( + options?: any, + sessionId?: string | undefined, + instanceId: number = 0, +): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + return await client.getAllOrganizations(options, sessionId ?? ((await getSessionId(instanceId)) as string)); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to get all the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, + 'getAllOrganizations-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default getAllOrganizations; diff --git a/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts b/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts index a355ab90c..4aaff4b9f 100644 --- a/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts +++ b/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts @@ -1,49 +1,50 @@ -/** - * 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. - */ - -'use server'; - -import {Organization} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const getCurrentOrganizationAction = async ( - sessionId: string, -): Promise<{ - data: {organization?: Organization; user?: Record}; - error: string | null; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const organization: Organization = (await client.getCurrentOrganization(sessionId)) as Organization; - return {data: {organization}, error: null, success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: 'Failed to get the current organization', - success: false, - }; - } -}; - -export default getCurrentOrganizationAction; +/** + * 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. + */ + +'use server'; + +import {Organization} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const getCurrentOrganizationAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{ + data: {organization?: Organization; user?: Record}; + error: string | null; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const organization: Organization = (await client.getCurrentOrganization(sessionId)) as Organization; + return {data: {organization}, error: null, success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: 'Failed to get the current organization', + success: false, + }; + } +}; + +export default getCurrentOrganizationAction; diff --git a/packages/nextjs/src/server/actions/getMyOrganizations.ts b/packages/nextjs/src/server/actions/getMyOrganizations.ts index eb9f62aee..13fb988a6 100644 --- a/packages/nextjs/src/server/actions/getMyOrganizations.ts +++ b/packages/nextjs/src/server/actions/getMyOrganizations.ts @@ -1,82 +1,82 @@ -/** - * 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. - */ - -'use server'; - -import {AsgardeoAPIError, Organization} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get organizations. - */ -const getMyOrganizations = async (options?: any, sessionId?: string | undefined): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - - // Get session ID if not provided - let resolvedSessionId: string | undefined = sessionId; - if (!resolvedSessionId) { - // Import getSessionId locally to avoid circular dependencies - const {default: getSessionId} = await import('./getSessionId'); - resolvedSessionId = await getSessionId(); - } - - if (!resolvedSessionId) { - throw new AsgardeoAPIError( - 'No session ID available for fetching organizations', - 'getMyOrganizations-SessionError-001', - 'nextjs', - 401, - ); - } - - // Check if user is signed in by trying to get access token - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - - if (!accessToken) { - throw new AsgardeoAPIError( - 'No access token available - user is not signed in', - 'getMyOrganizations-NoAccessToken-001', - 'nextjs', - 401, - ); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('[getMyOrganizations] Failed to get access token:', error); - throw new AsgardeoAPIError( - 'User is not signed in - access token retrieval failed', - 'getMyOrganizations-NotSignedIn-001', - 'nextjs', - 401, - ); - } - - return await client.getMyOrganizations(options, resolvedSessionId); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to get the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, - 'getMyOrganizations-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default getMyOrganizations; +/** + * 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. + */ + +'use server'; + +import {AsgardeoAPIError, Organization} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get organizations. + */ +const getMyOrganizations = async (options?: any, sessionId?: string | undefined, instanceId: number = 0): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + // Get session ID if not provided + let resolvedSessionId: string | undefined = sessionId; + if (!resolvedSessionId) { + // Import getSessionId locally to avoid circular dependencies + const {default: getSessionId} = await import('./getSessionId'); + resolvedSessionId = await getSessionId(instanceId); + } + + if (!resolvedSessionId) { + throw new AsgardeoAPIError( + 'No session ID available for fetching organizations', + 'getMyOrganizations-SessionError-001', + 'nextjs', + 401, + ); + } + + // Check if user is signed in by trying to get access token + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + + if (!accessToken) { + throw new AsgardeoAPIError( + 'No access token available - user is not signed in', + 'getMyOrganizations-NoAccessToken-001', + 'nextjs', + 401, + ); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('[getMyOrganizations] Failed to get access token:', error); + throw new AsgardeoAPIError( + 'User is not signed in - access token retrieval failed', + 'getMyOrganizations-NotSignedIn-001', + 'nextjs', + 401, + ); + } + + return await client.getMyOrganizations(options, resolvedSessionId); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to get the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, + 'getMyOrganizations-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default getMyOrganizations; diff --git a/packages/nextjs/src/server/actions/getOrganizationAction.ts b/packages/nextjs/src/server/actions/getOrganizationAction.ts index d83491e17..9e0383617 100644 --- a/packages/nextjs/src/server/actions/getOrganizationAction.ts +++ b/packages/nextjs/src/server/actions/getOrganizationAction.ts @@ -1,50 +1,51 @@ -/** - * 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. - */ - -'use server'; - -import {OrganizationDetails} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const getOrganizationAction = async ( - organizationId: string, - sessionId: string, -): Promise<{ - data: {organization?: OrganizationDetails; user?: Record}; - error: string | null; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const organization: OrganizationDetails = await client.getOrganization(organizationId, sessionId); - return {data: {organization}, error: null, success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: 'Failed to get organization', - success: false, - }; - } -}; - -export default getOrganizationAction; +/** + * 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. + */ + +'use server'; + +import {OrganizationDetails} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const getOrganizationAction = async ( + organizationId: string, + sessionId: string, + instanceId: number = 0, +): Promise<{ + data: {organization?: OrganizationDetails; user?: Record}; + error: string | null; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const organization: OrganizationDetails = await client.getOrganization(organizationId, sessionId); + return {data: {organization}, error: null, success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: 'Failed to get organization', + success: false, + }; + } +}; + +export default getOrganizationAction; diff --git a/packages/nextjs/src/server/actions/getSessionId.ts b/packages/nextjs/src/server/actions/getSessionId.ts index e3b598c30..f1843657c 100644 --- a/packages/nextjs/src/server/actions/getSessionId.ts +++ b/packages/nextjs/src/server/actions/getSessionId.ts @@ -1,49 +1,49 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the session ID from cookies. - * Tries JWT session first, then falls back to legacy session ID. - * - * @returns The session ID if it exists, undefined otherwise - */ -const getSessionId = async (): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName())?.value; - - if (sessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); - - return sessionPayload.sessionId; - } catch (error) { - return undefined; - } - } - - return undefined; -}; - -export default getSessionId; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the session ID from cookies. + * Tries JWT session first, then falls back to legacy session ID. + * + * @returns The session ID if it exists, undefined otherwise + */ +const getSessionId = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (sessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); + + return sessionPayload.sessionId; + } catch (error) { + return undefined; + } + } + + return undefined; +}; + +export default getSessionId; diff --git a/packages/nextjs/src/server/actions/getSessionPayload.ts b/packages/nextjs/src/server/actions/getSessionPayload.ts index 7cdea1d73..55423c74c 100644 --- a/packages/nextjs/src/server/actions/getSessionPayload.ts +++ b/packages/nextjs/src/server/actions/getSessionPayload.ts @@ -1,46 +1,46 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the session payload from JWT session cookie. - * This includes user ID, session ID, scopes, and organization ID. - * - * @returns The session payload if valid JWT session exists, undefined otherwise - */ -const getSessionPayload = async (): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName())?.value; - if (!sessionToken) { - return undefined; - } - - try { - return await SessionManager.verifySessionToken(sessionToken); - } catch { - return undefined; - } -}; - -export default getSessionPayload; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the session payload from JWT session cookie. + * This includes user ID, session ID, scopes, and organization ID. + * + * @returns The session payload if valid JWT session exists, undefined otherwise + */ +const getSessionPayload = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return undefined; + } + + try { + return await SessionManager.verifySessionToken(sessionToken); + } catch { + return undefined; + } +}; + +export default getSessionPayload; diff --git a/packages/nextjs/src/server/actions/getUserAction.ts b/packages/nextjs/src/server/actions/getUserAction.ts index a0ad5e4ba..db328fdc9 100644 --- a/packages/nextjs/src/server/actions/getUserAction.ts +++ b/packages/nextjs/src/server/actions/getUserAction.ts @@ -1,40 +1,41 @@ -/** - * 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. - */ - -'use server'; - -import {User} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const getUserAction = async ( - sessionId: string, -): Promise<{data: {user: User | null}; error: string | null; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const user: User = await client.getUser(sessionId); - return {data: {user}, error: null, success: true}; - } catch (error) { - return {data: {user: null}, error: 'Failed to get user', success: false}; - } -}; - -export default getUserAction; +/** + * 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. + */ + +'use server'; + +import {User} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const getUserAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{data: {user: User | null}; error: string | null; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const user: User = await client.getUser(sessionId); + return {data: {user}, error: null, success: true}; + } catch (error) { + return {data: {user: null}, error: 'Failed to get user', success: false}; + } +}; + +export default getUserAction; diff --git a/packages/nextjs/src/server/actions/getUserProfileAction.ts b/packages/nextjs/src/server/actions/getUserProfileAction.ts index 983bc0375..b2808ae20 100644 --- a/packages/nextjs/src/server/actions/getUserProfileAction.ts +++ b/packages/nextjs/src/server/actions/getUserProfileAction.ts @@ -1,50 +1,51 @@ -/** - * 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. - */ - -'use server'; - -import {UserProfile} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const getUserProfileAction = async ( - sessionId: string, -): Promise<{data: {userProfile: UserProfile}; error: string | null; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const updatedProfile: UserProfile = await client.getUserProfile(sessionId); - return {data: {userProfile: updatedProfile}, error: null, success: true}; - } catch (error) { - return { - data: { - userProfile: { - flattenedProfile: {}, - profile: {}, - schemas: [], - }, - }, - error: 'Failed to get user profile', - success: false, - }; - } -}; - -export default getUserProfileAction; +/** + * 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. + */ + +'use server'; + +import {UserProfile} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const getUserProfileAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{data: {userProfile: UserProfile}; error: string | null; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const updatedProfile: UserProfile = await client.getUserProfile(sessionId); + return {data: {userProfile: updatedProfile}, error: null, success: true}; + } catch (error) { + return { + data: { + userProfile: { + flattenedProfile: {}, + profile: {}, + schemas: [], + }, + }, + error: 'Failed to get user profile', + success: false, + }; + } +}; + +export default getUserProfileAction; diff --git a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts index 5acfdbbb1..7f81d60c9 100644 --- a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts +++ b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts @@ -1,148 +1,149 @@ -/** - * 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. - */ - -'use server'; - -import {IdToken} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import {AsgardeoNextConfig} from '../../models/config'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action to handle OAuth callback with authorization code. - * This action processes the authorization code received from the OAuth provider - * and exchanges it for tokens to complete the authentication flow. - * - * @param code - Authorization code from OAuth provider - * @param state - State parameter from OAuth provider for CSRF protection - * @param sessionState - Session state parameter from OAuth provider - * @returns Promise that resolves with success status and optional error message - */ -const handleOAuthCallbackAction = async ( - code: string, - state: string, - sessionState?: string, -): Promise<{ - error?: string; - redirectUrl?: string; - success: boolean; -}> => { - try { - if (!code || !state) { - return { - error: 'Missing required OAuth parameters: code and state are required', - success: false, - }; - } - - const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - - if (!asgardeoClient.isInitialized) { - return { - error: 'Asgardeo client is not initialized', - success: false, - }; - } - - const cookieStore: ReadonlyRequestCookies = await cookies(); - let sessionId: string | undefined; - - const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName())?.value; - - if (tempSessionToken) { - try { - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); - sessionId = tempSession.sessionId; - } catch { - logger.error( - '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.', - ); - } - } - - if (!sessionId) { - logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.'); - - return { - error: 'No session found. Please start the authentication flow again.', - success: false, - }; - } - - // Exchange the authorization code for tokens - const signInResult: Record = await asgardeoClient.signIn( - { - code, - session_state: sessionState, - state, - } as any, - {}, - sessionId, - ); - - if (signInResult) { - try { - const idToken: IdToken = await asgardeoClient.getDecodedIdToken( - sessionId, - (signInResult['id_token'] || signInResult['idToken']) as string, - ); - const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string; - const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string; - const scopes: string = signInResult['scope'] as string; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken, - sessionId, - scopes, - organizationId, - ); - - cookieStore.set(SessionManager.getSessionCookieName(), sessionToken, SessionManager.getSessionCookieOptions()); - - cookieStore.delete(SessionManager.getTempSessionCookieName()); - } catch (error) { - logger.error( - `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session: - ${typeof error === 'string' ? error : JSON.stringify(error)}`, - ); - } - } - - const config: AsgardeoNextConfig = await asgardeoClient.getConfiguration(); - const afterSignInUrl: string = config.afterSignInUrl || '/'; - - return { - redirectUrl: afterSignInUrl, - success: true, - }; - } catch (error) { - return { - error: error instanceof Error ? error.message : 'Authentication failed', - success: false, - }; - } -}; - -export default handleOAuthCallbackAction; +/** + * 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. + */ + +'use server'; + +import {IdToken} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import {AsgardeoNextConfig} from '../../models/config'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action to handle OAuth callback with authorization code. + * This action processes the authorization code received from the OAuth provider + * and exchanges it for tokens to complete the authentication flow. + * + * @param code - Authorization code from OAuth provider + * @param state - State parameter from OAuth provider for CSRF protection + * @param sessionState - Session state parameter from OAuth provider + * @returns Promise that resolves with success status and optional error message + */ +const handleOAuthCallbackAction = async ( + code: string, + state: string, + sessionState?: string, + instanceId: number = 0, +): Promise<{ + error?: string; + redirectUrl?: string; + success: boolean; +}> => { + try { + if (!code || !state) { + return { + error: 'Missing required OAuth parameters: code and state are required', + success: false, + }; + } + + const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + if (!asgardeoClient.isInitialized) { + return { + error: 'Asgardeo client is not initialized', + success: false, + }; + } + + const cookieStore: ReadonlyRequestCookies = await cookies(); + let sessionId: string | undefined; + + const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + + if (tempSessionToken) { + try { + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); + sessionId = tempSession.sessionId; + } catch { + logger.error( + '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.', + ); + } + } + + if (!sessionId) { + logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.'); + + return { + error: 'No session found. Please start the authentication flow again.', + success: false, + }; + } + + // Exchange the authorization code for tokens + const signInResult: Record = await asgardeoClient.signIn( + { + code, + session_state: sessionState, + state, + } as any, + {}, + sessionId, + ); + + if (signInResult) { + try { + const idToken: IdToken = await asgardeoClient.getDecodedIdToken( + sessionId, + (signInResult['id_token'] || signInResult['idToken']) as string, + ); + const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string; + const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string; + const scopes: string = signInResult['scope'] as string; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken, + sessionId, + scopes, + organizationId, + ); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + } catch (error) { + logger.error( + `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session: + ${typeof error === 'string' ? error : JSON.stringify(error)}`, + ); + } + } + + const config: AsgardeoNextConfig = await asgardeoClient.getConfiguration(); + const afterSignInUrl: string = config.afterSignInUrl || '/'; + + return { + redirectUrl: afterSignInUrl, + success: true, + }; + } catch (error) { + return { + error: error instanceof Error ? error.message : 'Authentication failed', + success: false, + }; + } +}; + +export default handleOAuthCallbackAction; diff --git a/packages/nextjs/src/server/actions/isSignedIn.ts b/packages/nextjs/src/server/actions/isSignedIn.ts index 6c0d43780..670c4d2ea 100644 --- a/packages/nextjs/src/server/actions/isSignedIn.ts +++ b/packages/nextjs/src/server/actions/isSignedIn.ts @@ -1,71 +1,71 @@ -/** - * 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. - */ - -'use server'; - -import getSessionId from './getSessionId'; -import getSessionPayload from './getSessionPayload'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Check if the user is currently signed in. - * First tries JWT session validation, then falls back to legacy session check. - * - * @param sessionId - Optional session ID to check (if not provided, gets from cookies) - * @returns True if user is signed in, false otherwise - */ -const isSignedIn = async (sessionId?: string): Promise => { - try { - const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(); - - if (sessionPayload) { - const resolvedSessionId: string = sessionPayload.sessionId; - - if (resolvedSessionId) { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - return !!accessToken; - } catch (error) { - return false; - } - } - } - - const resolvedSessionId: string | undefined = sessionId || (await getSessionId()); - - if (!resolvedSessionId) { - return false; - } - - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - - return !!accessToken; - } catch (error) { - return false; - } - } catch { - return false; - } -}; - -export default isSignedIn; +/** + * 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. + */ + +'use server'; + +import getSessionId from './getSessionId'; +import getSessionPayload from './getSessionPayload'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Check if the user is currently signed in. + * First tries JWT session validation, then falls back to legacy session check. + * + * @param sessionId - Optional session ID to check (if not provided, gets from cookies) + * @returns True if user is signed in, false otherwise + */ +const isSignedIn = async (sessionId?: string, instanceId: number = 0): Promise => { + try { + const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(instanceId); + + if (sessionPayload) { + const resolvedSessionId: string = sessionPayload.sessionId; + + if (resolvedSessionId) { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + return !!accessToken; + } catch (error) { + return false; + } + } + } + + const resolvedSessionId: string | undefined = sessionId || (await getSessionId(instanceId)); + + if (!resolvedSessionId) { + return false; + } + + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + + return !!accessToken; + } catch (error) { + return false; + } + } catch { + return false; + } +}; + +export default isSignedIn; diff --git a/packages/nextjs/src/server/actions/signInAction.ts b/packages/nextjs/src/server/actions/signInAction.ts index 98ca3412e..0dc5f4bc4 100644 --- a/packages/nextjs/src/server/actions/signInAction.ts +++ b/packages/nextjs/src/server/actions/signInAction.ts @@ -1,152 +1,153 @@ -/** - * 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. - */ - -'use server'; - -import { - generateSessionId, - EmbeddedSignInFlowStatus, - EmbeddedSignInFlowHandleRequestPayload, - EmbeddedFlowExecuteRequestConfig, - EmbeddedSignInFlowInitiateResponse, - IdToken, - isEmpty, -} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Server action for signing in a user. - * Handles the embedded sign-in flow and manages session cookies. - * - * @param payload - The embedded sign-in flow payload - * @param request - The embedded flow execute request config - * @returns Promise that resolves when sign-in is complete - */ -const signInAction = async ( - payload?: EmbeddedSignInFlowHandleRequestPayload, - request?: EmbeddedFlowExecuteRequestConfig, -): Promise<{ - data?: - | { - afterSignInUrl?: string; - signInUrl?: string; - } - | EmbeddedSignInFlowInitiateResponse; - error?: string; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const cookieStore: ReadonlyRequestCookies = await cookies(); - - let sessionId: string | undefined; - - const existingSessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName())?.value; - - if (existingSessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken); - sessionId = sessionPayload.sessionId; - } catch { - // Invalid session token, will create new temp session - } - } - - if (!sessionId) { - const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName())?.value; - - if (tempSessionToken) { - try { - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); - sessionId = tempSession.sessionId; - } catch { - // Invalid temp session, will create new one - } - } - } - - if (!sessionId) { - sessionId = generateSessionId(); - - const tempSessionToken: string = await SessionManager.createTempSession(sessionId); - - cookieStore.set( - SessionManager.getTempSessionCookieName(), - tempSessionToken, - SessionManager.getTempSessionCookieOptions(), - ); - } - - // If no payload provided, redirect to sign-in URL for redirect-based sign-in. - if (!payload || isEmpty(payload)) { - const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId); - return {data: {signInUrl: String(defaultSignInUrl)}, success: true}; - } - - // Handle embedded sign-in flow - const response: any = await client.signIn(payload, request!, sessionId); - - if (response.flowStatus === EmbeddedSignInFlowStatus.SuccessCompleted) { - const signInResult: Record = await client.signIn( - { - code: response?.authData?.code, - session_state: response?.authData?.session_state, - state: response?.authData?.state, - } as any, - {}, - sessionId, - ); - - if (signInResult) { - const idToken: IdToken = await client.getDecodedIdToken(sessionId); - const userIdFromToken: string = (idToken['sub'] || signInResult['sub'] || sessionId) as string; - const {accessToken}: {accessToken: string} = signInResult as {accessToken: string}; - const scopes: string = signInResult['scope'] as string; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken, - sessionId as string, - scopes, - organizationId, - ); - - cookieStore.set(SessionManager.getSessionCookieName(), sessionToken, SessionManager.getSessionCookieOptions()); - - cookieStore.delete(SessionManager.getTempSessionCookieName()); - } - - const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); - return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true}; - } - - return {data: response as EmbeddedSignInFlowInitiateResponse, success: true}; - } catch (error) { - // eslint-disable-next-line no-console - console.error('[signInAction] Error during sign-in:', error); - return {error: String(error), success: false}; - } -}; - -export default signInAction; +/** + * 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. + */ + +'use server'; + +import { + generateSessionId, + EmbeddedSignInFlowStatus, + EmbeddedSignInFlowHandleRequestPayload, + EmbeddedFlowExecuteRequestConfig, + EmbeddedSignInFlowInitiateResponse, + IdToken, + isEmpty, +} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Server action for signing in a user. + * Handles the embedded sign-in flow and manages session cookies. + * + * @param payload - The embedded sign-in flow payload + * @param request - The embedded flow execute request config + * @returns Promise that resolves when sign-in is complete + */ +const signInAction = async ( + payload?: EmbeddedSignInFlowHandleRequestPayload, + request?: EmbeddedFlowExecuteRequestConfig, + instanceId: number = 0, +): Promise<{ + data?: + | { + afterSignInUrl?: string; + signInUrl?: string; + } + | EmbeddedSignInFlowInitiateResponse; + error?: string; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const cookieStore: ReadonlyRequestCookies = await cookies(); + + let sessionId: string | undefined; + + const existingSessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (existingSessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken); + sessionId = sessionPayload.sessionId; + } catch { + // Invalid session token, will create new temp session + } + } + + if (!sessionId) { + const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + + if (tempSessionToken) { + try { + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); + sessionId = tempSession.sessionId; + } catch { + // Invalid temp session, will create new one + } + } + } + + if (!sessionId) { + sessionId = generateSessionId(); + + const tempSessionToken: string = await SessionManager.createTempSession(sessionId); + + cookieStore.set( + SessionManager.getTempSessionCookieName(instanceId), + tempSessionToken, + SessionManager.getTempSessionCookieOptions(), + ); + } + + // If no payload provided, redirect to sign-in URL for redirect-based sign-in. + if (!payload || isEmpty(payload)) { + const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId); + return {data: {signInUrl: String(defaultSignInUrl)}, success: true}; + } + + // Handle embedded sign-in flow + const response: any = await client.signIn(payload, request!, sessionId); + + if (response.flowStatus === EmbeddedSignInFlowStatus.SuccessCompleted) { + const signInResult: Record = await client.signIn( + { + code: response?.authData?.code, + session_state: response?.authData?.session_state, + state: response?.authData?.state, + } as any, + {}, + sessionId, + ); + + if (signInResult) { + const idToken: IdToken = await client.getDecodedIdToken(sessionId); + const userIdFromToken: string = (idToken['sub'] || signInResult['sub'] || sessionId) as string; + const {accessToken}: {accessToken: string} = signInResult as {accessToken: string}; + const scopes: string = signInResult['scope'] as string; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken, + sessionId as string, + scopes, + organizationId, + ); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + } + + const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); + return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true}; + } + + return {data: response as EmbeddedSignInFlowInitiateResponse, success: true}; + } catch (error) { + // eslint-disable-next-line no-console + console.error('[signInAction] Error during sign-in:', error); + return {error: String(error), success: false}; + } +}; + +export default signInAction; diff --git a/packages/nextjs/src/server/actions/signOutAction.ts b/packages/nextjs/src/server/actions/signOutAction.ts index 6d76e13ea..641b1c873 100644 --- a/packages/nextjs/src/server/actions/signOutAction.ts +++ b/packages/nextjs/src/server/actions/signOutAction.ts @@ -1,82 +1,82 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action for signing out a user. - * Clears both JWT and legacy session cookies. - * - * @returns Promise that resolves with success status and optional after sign-out URL - */ -const signOutAction = async (): Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}> => { - logger.debug('[signOutAction] Initiating sign out process from the server action.'); - - const clearSessionCookies = async (): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - cookieStore.delete(SessionManager.getSessionCookieName()); - cookieStore.delete(SessionManager.getTempSessionCookieName()); - }; - - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const sessionId: string | undefined = await getSessionId(); - - let afterSignOutUrl: string = '/'; - - if (sessionId) { - logger.debug('[signOutAction] Session ID found, invoking the `signOut` to obtain the `afterSignOutUrl`.'); - - afterSignOutUrl = await client.signOut({}, sessionId); - } - - await clearSessionCookies(); - - return {data: {afterSignOutUrl}, success: true}; - } catch (error) { - logger.error('[signOutAction] Error during sign out from the server action:', error); - - logger.debug('[signOutAction] Clearing session cookies due to error as a fallback.'); - - await clearSessionCookies(); - - let errorMessage: unknown; - if (typeof error === 'string') { - errorMessage = error; - } else if (error instanceof Error) { - errorMessage = error.message; - } else { - errorMessage = JSON.stringify(error); - } - - return { - error: errorMessage, - success: false, - }; - } -}; - -export default signOutAction; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action for signing out a user. + * Clears both JWT and legacy session cookies. + * + * @returns Promise that resolves with success status and optional after sign-out URL + */ +const signOutAction = async (instanceId: number = 0): Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}> => { + logger.debug('[signOutAction] Initiating sign out process from the server action.'); + + const clearSessionCookies = async (): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + cookieStore.delete(SessionManager.getSessionCookieName(instanceId)); + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + }; + + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const sessionId: string | undefined = await getSessionId(instanceId); + + let afterSignOutUrl: string = '/'; + + if (sessionId) { + logger.debug('[signOutAction] Session ID found, invoking the `signOut` to obtain the `afterSignOutUrl`.'); + + afterSignOutUrl = await client.signOut({}, sessionId); + } + + await clearSessionCookies(); + + return {data: {afterSignOutUrl}, success: true}; + } catch (error) { + logger.error('[signOutAction] Error during sign out from the server action:', error); + + logger.debug('[signOutAction] Clearing session cookies due to error as a fallback.'); + + await clearSessionCookies(); + + let errorMessage: unknown; + if (typeof error === 'string') { + errorMessage = error; + } else if (error instanceof Error) { + errorMessage = error.message; + } else { + errorMessage = JSON.stringify(error); + } + + return { + error: errorMessage, + success: false, + }; + } +}; + +export default signOutAction; diff --git a/packages/nextjs/src/server/actions/signUpAction.ts b/packages/nextjs/src/server/actions/signUpAction.ts index febd32c03..2a0c28dee 100644 --- a/packages/nextjs/src/server/actions/signUpAction.ts +++ b/packages/nextjs/src/server/actions/signUpAction.ts @@ -1,68 +1,69 @@ -/** - * 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. - */ - -'use server'; - -import {EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteResponse, EmbeddedFlowStatus} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action for signing in a user. - * Handles the embedded sign-in flow and manages session cookies. - * - * @param payload - The embedded sign-in flow payload - * @param request - The embedded flow execute request config - * @returns Promise that resolves when sign-in is complete - */ -const signUpAction = async ( - payload?: EmbeddedFlowExecuteRequestPayload, -): Promise<{ - data?: - | { - afterSignUpUrl?: string; - signUpUrl?: string; - } - | EmbeddedFlowExecuteResponse; - error?: string; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - - // If no payload provided, redirect to sign-in URL for redirect-based sign-in. - // If there's a payload, handle the embedded sign-in flow. - if (!payload) { - const defaultSignUpUrl: string = ''; - - return {data: {signUpUrl: String(defaultSignUpUrl)}, success: true}; - } - const response: any = await client.signUp(payload); - - if (response.flowStatus === EmbeddedFlowStatus.Complete) { - const afterSignUpUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); - - return {data: {afterSignUpUrl: String(afterSignUpUrl)}, success: true}; - } - - return {data: response as EmbeddedFlowExecuteResponse, success: true}; - } catch (error) { - return {error: String(error), success: false}; - } -}; - -export default signUpAction; +/** + * 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. + */ + +'use server'; + +import {EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteResponse, EmbeddedFlowStatus} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action for signing in a user. + * Handles the embedded sign-in flow and manages session cookies. + * + * @param payload - The embedded sign-in flow payload + * @param request - The embedded flow execute request config + * @returns Promise that resolves when sign-in is complete + */ +const signUpAction = async ( + payload?: EmbeddedFlowExecuteRequestPayload, + instanceId: number = 0, +): Promise<{ + data?: + | { + afterSignUpUrl?: string; + signUpUrl?: string; + } + | EmbeddedFlowExecuteResponse; + error?: string; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + // If no payload provided, redirect to sign-in URL for redirect-based sign-in. + // If there's a payload, handle the embedded sign-in flow. + if (!payload) { + const defaultSignUpUrl: string = ''; + + return {data: {signUpUrl: String(defaultSignUpUrl)}, success: true}; + } + const response: any = await client.signUp(payload); + + if (response.flowStatus === EmbeddedFlowStatus.Complete) { + const afterSignUpUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); + + return {data: {afterSignUpUrl: String(afterSignUpUrl)}, success: true}; + } + + return {data: response as EmbeddedFlowExecuteResponse, success: true}; + } catch (error) { + return {error: String(error), success: false}; + } +}; + +export default signUpAction; diff --git a/packages/nextjs/src/server/actions/switchOrganization.ts b/packages/nextjs/src/server/actions/switchOrganization.ts index 64d095051..d77ef6c5d 100644 --- a/packages/nextjs/src/server/actions/switchOrganization.ts +++ b/packages/nextjs/src/server/actions/switchOrganization.ts @@ -1,82 +1,83 @@ -/** - * 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. - */ - -'use server'; - -import {Organization, AsgardeoAPIError, IdToken, TokenResponse} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action to switch organization. - */ -const switchOrganization = async ( - organization: Organization, - sessionId: string | undefined, -): Promise => { - try { - const cookieStore: ReadonlyRequestCookies = await cookies(); - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const resolvedSessionId: string = sessionId ?? ((await getSessionId()) as string); - const response: TokenResponse | Response = await client.switchOrganization(organization, resolvedSessionId); - - // After switching organization, we need to refresh the page to get updated session data - // This is because server components don't maintain state between function calls - const {revalidatePath} = await import('next/cache'); - - // Revalidate the current path to refresh the component with new data - revalidatePath('/'); - - if (response) { - const idToken: IdToken = await client.getDecodedIdToken(resolvedSessionId, (response as TokenResponse).idToken); - const userIdFromToken: string = idToken['sub'] as string; - const {accessToken}: {accessToken: string} = response as TokenResponse; - const scopes: string = (response as TokenResponse).scope; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken as string, - resolvedSessionId as string, - scopes, - organizationId, - ); - - logger.debug('[switchOrganization] Session token created successfully.'); - - cookieStore.set(SessionManager.getSessionCookieName(), sessionToken, SessionManager.getSessionCookieOptions()); - } - - return response; - } catch (error) { - throw new AsgardeoAPIError( - `Failed to switch the organizations: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, - 'switchOrganization-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default switchOrganization; +/** + * 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. + */ + +'use server'; + +import {Organization, AsgardeoAPIError, IdToken, TokenResponse} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action to switch organization. + */ +const switchOrganization = async ( + organization: Organization, + sessionId: string | undefined, + instanceId: number = 0, +): Promise => { + try { + const cookieStore: ReadonlyRequestCookies = await cookies(); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const resolvedSessionId: string = sessionId ?? ((await getSessionId(instanceId)) as string); + const response: TokenResponse | Response = await client.switchOrganization(organization, resolvedSessionId); + + // After switching organization, we need to refresh the page to get updated session data + // This is because server components don't maintain state between function calls + const {revalidatePath} = await import('next/cache'); + + // Revalidate the current path to refresh the component with new data + revalidatePath('/'); + + if (response) { + const idToken: IdToken = await client.getDecodedIdToken(resolvedSessionId, (response as TokenResponse).idToken); + const userIdFromToken: string = idToken['sub'] as string; + const {accessToken}: {accessToken: string} = response as TokenResponse; + const scopes: string = (response as TokenResponse).scope; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken as string, + resolvedSessionId as string, + scopes, + organizationId, + ); + + logger.debug('[switchOrganization] Session token created successfully.'); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + } + + return response; + } catch (error) { + throw new AsgardeoAPIError( + `Failed to switch the organizations: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'switchOrganization-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default switchOrganization; diff --git a/packages/nextjs/src/server/actions/updateUserProfileAction.ts b/packages/nextjs/src/server/actions/updateUserProfileAction.ts index 33937b376..f2124ccc7 100644 --- a/packages/nextjs/src/server/actions/updateUserProfileAction.ts +++ b/packages/nextjs/src/server/actions/updateUserProfileAction.ts @@ -1,47 +1,48 @@ -/** - * 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. - */ - -'use server'; - -import {UpdateMeProfileConfig, User} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const updateUserProfileAction = async ( - payload: UpdateMeProfileConfig, - sessionId?: string, -): Promise<{data: {user: User}; error: string; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); - const user: User = await client.updateUserProfile(payload, sessionId); - return {data: {user}, error: '', success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: `Failed to get user profile: ${error instanceof Error ? error.message : String(error)}`, - success: false, - }; - } -}; - -export default updateUserProfileAction; +/** + * 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. + */ + +'use server'; + +import {UpdateMeProfileConfig, User} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const updateUserProfileAction = async ( + payload: UpdateMeProfileConfig, + sessionId?: string, + instanceId: number = 0, +): Promise<{data: {user: User}; error: string; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const user: User = await client.updateUserProfile(payload, sessionId); + return {data: {user}, error: '', success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: `Failed to get user profile: ${error instanceof Error ? error.message : String(error)}`, + success: false, + }; + } +}; + +export default updateUserProfileAction; From 8d88db49a5cb359773df8c75b96efa25506c6ec2 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Wed, 11 Mar 2026 12:27:02 +0530 Subject: [PATCH 3/7] Change crlf to lf - Refactor createOrganization function to improve parameter order and maintain code consistency --- .../src/server/actions/createOrganization.ts | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/nextjs/src/server/actions/createOrganization.ts b/packages/nextjs/src/server/actions/createOrganization.ts index 5249c3fcf..c0f779dae 100644 --- a/packages/nextjs/src/server/actions/createOrganization.ts +++ b/packages/nextjs/src/server/actions/createOrganization.ts @@ -1,42 +1,42 @@ -/** - * 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. - */ - -'use server'; - -import {CreateOrganizationPayload, Organization, AsgardeoAPIError} from '@asgardeo/node'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const createOrganization = async (payload: CreateOrganizationPayload, sessionId: string, instanceId: number = 0): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - return await client.createOrganization(payload, sessionId ?? ((await getSessionId(instanceId)) as string)); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to create the organization: ${error instanceof Error ? error.message : String(error)}`, - 'createOrganization-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default createOrganization; +/** + * 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. + */ + +'use server'; + +import {CreateOrganizationPayload, Organization, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const createOrganization = async (instanceId: number = 0, payload: CreateOrganizationPayload, sessionId: string): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + return await client.createOrganization(payload, sessionId ?? ((await getSessionId(instanceId)) as string)); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to create the organization: ${error instanceof Error ? error.message : String(error)}`, + 'createOrganization-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default createOrganization; From ae5250c1917e5c6cab012373538dcac07c77239f Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Wed, 11 Mar 2026 12:31:45 +0530 Subject: [PATCH 4/7] Refactor server actions to include instanceId parameter and improve session handling - Updated getAllOrganizations, handleOAuthCallbackAction, signInAction, signUpAction, switchOrganization, and updateUserProfileAction to accept instanceId as a parameter for better session management. - Enhanced asgardeo middleware to support instanceId, ensuring consistent session handling across requests. - Improved error handling and logging in various actions to provide clearer feedback on failures. --- .../nextjs/src/server/AsgardeoProvider.tsx | 53 ++- .../__tests__/createOrganization.test.ts | 10 +- .../__tests__/getAllOrganizations.test.ts | 10 +- .../src/server/actions/getAllOrganizations.ts | 92 +++--- .../actions/handleOAuthCallbackAction.ts | 298 ++++++++--------- .../nextjs/src/server/actions/signInAction.ts | 306 +++++++++--------- .../nextjs/src/server/actions/signUpAction.ts | 138 ++++---- .../src/server/actions/switchOrganization.ts | 166 +++++----- .../server/actions/updateUserProfileAction.ts | 96 +++--- packages/nextjs/src/server/asgardeo.ts | 10 +- .../server/middleware/asgardeoMiddleware.ts | 22 +- 11 files changed, 612 insertions(+), 589 deletions(-) diff --git a/packages/nextjs/src/server/AsgardeoProvider.tsx b/packages/nextjs/src/server/AsgardeoProvider.tsx index d4760f7b4..d198b23ea 100644 --- a/packages/nextjs/src/server/AsgardeoProvider.tsx +++ b/packages/nextjs/src/server/AsgardeoProvider.tsx @@ -18,7 +18,14 @@ 'use server'; -import {BrandingPreference, AsgardeoRuntimeError, IdToken, Organization, User, UserProfile} from '@asgardeo/node'; +import { + BrandingPreference, + AsgardeoRuntimeError, + IdToken, + Organization, + User, + UserProfile, +} from '@asgardeo/node'; import {AsgardeoProviderProps} from '@asgardeo/react'; import {FC, PropsWithChildren, ReactElement} from 'react'; import createOrganization from './actions/createOrganization'; @@ -70,9 +77,10 @@ const AsgardeoServerProvider: FC> children, afterSignInUrl, afterSignOutUrl, + instanceId = 0, ..._config }: PropsWithChildren): Promise => { - const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); let config: Partial = {}; try { @@ -97,9 +105,9 @@ const AsgardeoServerProvider: FC> } // Try to get session information from JWT first, then fall back to legacy - const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(); - const sessionId: string = sessionPayload?.sessionId || (await getSessionId()) || ''; - const signedIn: boolean = sessionPayload ? true : await isSignedIn(sessionId); + const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(instanceId); + const sessionId: string = sessionPayload?.sessionId || (await getSessionId(instanceId)) || ''; + const signedIn: boolean = sessionPayload ? true : await isSignedIn(sessionId, instanceId); let user: User = {}; let userProfile: UserProfile = { @@ -138,20 +146,20 @@ const AsgardeoServerProvider: FC> data: {user: User | null}; error: string | null; success: boolean; - } = await getUserAction(sessionId); + } = await getUserAction(sessionId, instanceId); const userProfileResponse: { data: {userProfile: UserProfile}; error: string | null; success: boolean; - } = await getUserProfileAction(sessionId); + } = await getUserProfileAction(sessionId, instanceId); const currentOrganizationResponse: { data: {organization?: Organization; user?: Record}; error: string | null; success: boolean; - } = await getCurrentOrganizationAction(sessionId); + } = await getCurrentOrganizationAction(sessionId, instanceId); if (sessionId) { - myOrganizations = await getMyOrganizations({}, sessionId); + myOrganizations = await getMyOrganizations({}, sessionId, instanceId); } else { // eslint-disable-next-line no-console console.warn('[AsgardeoServerProvider] No session ID available, skipping organization fetch'); @@ -186,15 +194,26 @@ const AsgardeoServerProvider: FC> } } + // Create instance-bound server actions using .bind() so React can serialize them + // as proper server action references when passed to Client Components. + const boundSignIn = signInAction.bind(null, instanceId); + const boundSignOut = signOutAction.bind(null, instanceId); + const boundSignUp = signUpAction.bind(null, instanceId); + const boundHandleOAuthCallback = handleOAuthCallbackAction.bind(null, instanceId); + const boundSwitchOrganization = switchOrganization.bind(null, instanceId); + const boundGetAllOrganizations = getAllOrganizations.bind(null, instanceId); + const boundCreateOrganization = createOrganization.bind(null, instanceId); + const boundUpdateUserProfile = updateUserProfileAction.bind(null, instanceId); + return ( > user={user} currentOrganization={currentOrganization} userProfile={userProfile} - updateProfile={updateUserProfileAction} + updateProfile={boundUpdateUserProfile} isSignedIn={signedIn} myOrganizations={myOrganizations} - getAllOrganizations={getAllOrganizations} - switchOrganization={switchOrganization} + getAllOrganizations={boundGetAllOrganizations} + switchOrganization={boundSwitchOrganization} brandingPreference={brandingPreference} - createOrganization={createOrganization} + createOrganization={boundCreateOrganization} > {children} diff --git a/packages/nextjs/src/server/actions/__tests__/createOrganization.test.ts b/packages/nextjs/src/server/actions/__tests__/createOrganization.test.ts index 6367ce537..93e904e8a 100644 --- a/packages/nextjs/src/server/actions/__tests__/createOrganization.test.ts +++ b/packages/nextjs/src/server/actions/__tests__/createOrganization.test.ts @@ -77,7 +77,7 @@ describe('createOrganization (Next.js server action)', () => { it('should create an organization successfully when a sessionId is provided', async () => { mockClient.createOrganization.mockResolvedValueOnce(mockOrg); - const result: Organization = await createOrganization(basePayload, 'sess-123'); + const result: Organization = await createOrganization(0, basePayload, 'sess-123'); expect(AsgardeoNextClient.getInstance).toHaveBeenCalledTimes(1); expect(getSessionId).not.toHaveBeenCalled(); @@ -88,7 +88,7 @@ describe('createOrganization (Next.js server action)', () => { it('should fall back to getSessionId when sessionId is undefined', async () => { mockClient.createOrganization.mockResolvedValueOnce(mockOrg); - const result: Organization = await createOrganization(basePayload, undefined as unknown as string); + const result: Organization = await createOrganization(0, basePayload, undefined as unknown as string); expect(getSessionId).toHaveBeenCalledTimes(1); expect(mockClient.createOrganization).toHaveBeenCalledWith(basePayload, 'sess-abc'); @@ -98,7 +98,7 @@ describe('createOrganization (Next.js server action)', () => { it('should fall back to getSessionId when sessionId is null', async () => { mockClient.createOrganization.mockResolvedValueOnce(mockOrg); - const result: Organization = await createOrganization(basePayload, null as unknown as string); + const result: Organization = await createOrganization(0, basePayload, null as unknown as string); expect(getSessionId).toHaveBeenCalledTimes(1); expect(mockClient.createOrganization).toHaveBeenCalledWith(basePayload, 'sess-abc'); @@ -108,7 +108,7 @@ describe('createOrganization (Next.js server action)', () => { it('should not call getSessionId when an empty string is passed (empty string is not nullish)', async () => { mockClient.createOrganization.mockResolvedValueOnce(mockOrg); - const result: Organization = await createOrganization(basePayload, ''); + const result: Organization = await createOrganization(0, basePayload, ''); expect(getSessionId).not.toHaveBeenCalled(); expect(mockClient.createOrganization).toHaveBeenCalledWith(basePayload, ''); @@ -124,7 +124,7 @@ describe('createOrganization (Next.js server action)', () => { ); mockClient.createOrganization.mockRejectedValueOnce(original); - await expect(createOrganization(basePayload, 'sess-1')).rejects.toMatchObject({ + await expect(createOrganization(0, basePayload, 'sess-1')).rejects.toMatchObject({ constructor: AsgardeoAPIError, message: expect.stringContaining('Failed to create the organization: Upstream validation failed'), statusCode: 400, diff --git a/packages/nextjs/src/server/actions/__tests__/getAllOrganizations.test.ts b/packages/nextjs/src/server/actions/__tests__/getAllOrganizations.test.ts index 196248c09..5f79c8bd5 100644 --- a/packages/nextjs/src/server/actions/__tests__/getAllOrganizations.test.ts +++ b/packages/nextjs/src/server/actions/__tests__/getAllOrganizations.test.ts @@ -71,7 +71,7 @@ describe('getAllOrganizations (Next.js server action)', () => { it('returns organizations when a sessionId is provided (no getSessionId fallback)', async () => { mockClient.getAllOrganizations.mockResolvedValueOnce(mockResponse); - const result: AllOrganizationsApiResponse = await getAllOrganizations(baseOptions, 'sess-123'); + const result: AllOrganizationsApiResponse = await getAllOrganizations(0, baseOptions, 'sess-123'); expect(AsgardeoNextClient.getInstance).toHaveBeenCalledTimes(1); expect(getSessionId).not.toHaveBeenCalled(); @@ -82,7 +82,7 @@ describe('getAllOrganizations (Next.js server action)', () => { it('falls back to getSessionId when sessionId is undefined', async () => { mockClient.getAllOrganizations.mockResolvedValueOnce(mockResponse); - const result: AllOrganizationsApiResponse = await getAllOrganizations(baseOptions, undefined); + const result: AllOrganizationsApiResponse = await getAllOrganizations(0, baseOptions, undefined); expect(getSessionId).toHaveBeenCalledTimes(1); expect(mockClient.getAllOrganizations).toHaveBeenCalledWith(baseOptions, 'sess-abc'); @@ -92,7 +92,7 @@ describe('getAllOrganizations (Next.js server action)', () => { it('falls back to getSessionId when sessionId is null', async () => { mockClient.getAllOrganizations.mockResolvedValueOnce(mockResponse); - const result: AllOrganizationsApiResponse = await getAllOrganizations(baseOptions, null as unknown as string); + const result: AllOrganizationsApiResponse = await getAllOrganizations(0, baseOptions, null as unknown as string); expect(getSessionId).toHaveBeenCalledTimes(1); expect(mockClient.getAllOrganizations).toHaveBeenCalledWith(baseOptions, 'sess-abc'); @@ -102,7 +102,7 @@ describe('getAllOrganizations (Next.js server action)', () => { it('does not call getSessionId for an empty string sessionId (empty string is not nullish)', async () => { mockClient.getAllOrganizations.mockResolvedValueOnce(mockResponse); - const result: AllOrganizationsApiResponse = await getAllOrganizations(baseOptions, ''); + const result: AllOrganizationsApiResponse = await getAllOrganizations(0, baseOptions, ''); expect(getSessionId).not.toHaveBeenCalled(); expect(mockClient.getAllOrganizations).toHaveBeenCalledWith(baseOptions, ''); @@ -113,7 +113,7 @@ describe('getAllOrganizations (Next.js server action)', () => { const upstream: AsgardeoAPIError = new AsgardeoAPIError('Upstream failed', 'ORG_LIST_500', 'server', 503); mockClient.getAllOrganizations.mockRejectedValueOnce(upstream); - await expect(getAllOrganizations(baseOptions, 'sess-x')).rejects.toMatchObject({ + await expect(getAllOrganizations(0, baseOptions, 'sess-x')).rejects.toMatchObject({ constructor: AsgardeoAPIError, message: expect.stringContaining('Failed to get all the organizations for the user: Upstream failed'), statusCode: 503, diff --git a/packages/nextjs/src/server/actions/getAllOrganizations.ts b/packages/nextjs/src/server/actions/getAllOrganizations.ts index 333fb16eb..751abceb8 100644 --- a/packages/nextjs/src/server/actions/getAllOrganizations.ts +++ b/packages/nextjs/src/server/actions/getAllOrganizations.ts @@ -1,46 +1,46 @@ -/** - * 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. - */ - -'use server'; - -import {AllOrganizationsApiResponse, AsgardeoAPIError} from '@asgardeo/node'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get organizations. - */ -const getAllOrganizations = async ( - options?: any, - sessionId?: string | undefined, - instanceId: number = 0, -): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - return await client.getAllOrganizations(options, sessionId ?? ((await getSessionId(instanceId)) as string)); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to get all the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, - 'getAllOrganizations-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default getAllOrganizations; +/** + * 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. + */ + +'use server'; + +import {AllOrganizationsApiResponse, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get organizations. + */ +const getAllOrganizations = async ( + instanceId: number = 0, + options?: any, + sessionId?: string | undefined, +): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + return await client.getAllOrganizations(options, sessionId ?? ((await getSessionId(instanceId)) as string)); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to get all the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, + 'getAllOrganizations-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default getAllOrganizations; diff --git a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts index 7f81d60c9..23266172f 100644 --- a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts +++ b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts @@ -1,149 +1,149 @@ -/** - * 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. - */ - -'use server'; - -import {IdToken} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import {AsgardeoNextConfig} from '../../models/config'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action to handle OAuth callback with authorization code. - * This action processes the authorization code received from the OAuth provider - * and exchanges it for tokens to complete the authentication flow. - * - * @param code - Authorization code from OAuth provider - * @param state - State parameter from OAuth provider for CSRF protection - * @param sessionState - Session state parameter from OAuth provider - * @returns Promise that resolves with success status and optional error message - */ -const handleOAuthCallbackAction = async ( - code: string, - state: string, - sessionState?: string, - instanceId: number = 0, -): Promise<{ - error?: string; - redirectUrl?: string; - success: boolean; -}> => { - try { - if (!code || !state) { - return { - error: 'Missing required OAuth parameters: code and state are required', - success: false, - }; - } - - const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - - if (!asgardeoClient.isInitialized) { - return { - error: 'Asgardeo client is not initialized', - success: false, - }; - } - - const cookieStore: ReadonlyRequestCookies = await cookies(); - let sessionId: string | undefined; - - const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; - - if (tempSessionToken) { - try { - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); - sessionId = tempSession.sessionId; - } catch { - logger.error( - '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.', - ); - } - } - - if (!sessionId) { - logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.'); - - return { - error: 'No session found. Please start the authentication flow again.', - success: false, - }; - } - - // Exchange the authorization code for tokens - const signInResult: Record = await asgardeoClient.signIn( - { - code, - session_state: sessionState, - state, - } as any, - {}, - sessionId, - ); - - if (signInResult) { - try { - const idToken: IdToken = await asgardeoClient.getDecodedIdToken( - sessionId, - (signInResult['id_token'] || signInResult['idToken']) as string, - ); - const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string; - const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string; - const scopes: string = signInResult['scope'] as string; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken, - sessionId, - scopes, - organizationId, - ); - - cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); - - cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); - } catch (error) { - logger.error( - `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session: - ${typeof error === 'string' ? error : JSON.stringify(error)}`, - ); - } - } - - const config: AsgardeoNextConfig = await asgardeoClient.getConfiguration(); - const afterSignInUrl: string = config.afterSignInUrl || '/'; - - return { - redirectUrl: afterSignInUrl, - success: true, - }; - } catch (error) { - return { - error: error instanceof Error ? error.message : 'Authentication failed', - success: false, - }; - } -}; - -export default handleOAuthCallbackAction; +/** + * 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. + */ + +'use server'; + +import {IdToken} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import {AsgardeoNextConfig} from '../../models/config'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action to handle OAuth callback with authorization code. + * This action processes the authorization code received from the OAuth provider + * and exchanges it for tokens to complete the authentication flow. + * + * @param code - Authorization code from OAuth provider + * @param state - State parameter from OAuth provider for CSRF protection + * @param sessionState - Session state parameter from OAuth provider + * @returns Promise that resolves with success status and optional error message + */ +const handleOAuthCallbackAction = async ( + instanceId: number = 0, + code: string, + state: string, + sessionState?: string, +): Promise<{ + error?: string; + redirectUrl?: string; + success: boolean; +}> => { + try { + if (!code || !state) { + return { + error: 'Missing required OAuth parameters: code and state are required', + success: false, + }; + } + + const asgardeoClient: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + if (!asgardeoClient.isInitialized) { + return { + error: 'Asgardeo client is not initialized', + success: false, + }; + } + + const cookieStore: ReadonlyRequestCookies = await cookies(); + let sessionId: string | undefined; + + const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + + if (tempSessionToken) { + try { + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); + sessionId = tempSession.sessionId; + } catch { + logger.error( + '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.', + ); + } + } + + if (!sessionId) { + logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.'); + + return { + error: 'No session found. Please start the authentication flow again.', + success: false, + }; + } + + // Exchange the authorization code for tokens + const signInResult: Record = await asgardeoClient.signIn( + { + code, + session_state: sessionState, + state, + } as any, + {}, + sessionId, + ); + + if (signInResult) { + try { + const idToken: IdToken = await asgardeoClient.getDecodedIdToken( + sessionId, + (signInResult['id_token'] || signInResult['idToken']) as string, + ); + const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string; + const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string; + const scopes: string = signInResult['scope'] as string; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken, + sessionId, + scopes, + organizationId, + ); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + } catch (error) { + logger.error( + `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session: + ${typeof error === 'string' ? error : JSON.stringify(error)}`, + ); + } + } + + const config: AsgardeoNextConfig = await asgardeoClient.getConfiguration(); + const afterSignInUrl: string = config.afterSignInUrl || '/'; + + return { + redirectUrl: afterSignInUrl, + success: true, + }; + } catch (error) { + return { + error: error instanceof Error ? error.message : 'Authentication failed', + success: false, + }; + } +}; + +export default handleOAuthCallbackAction; diff --git a/packages/nextjs/src/server/actions/signInAction.ts b/packages/nextjs/src/server/actions/signInAction.ts index 0dc5f4bc4..b7d210f57 100644 --- a/packages/nextjs/src/server/actions/signInAction.ts +++ b/packages/nextjs/src/server/actions/signInAction.ts @@ -1,153 +1,153 @@ -/** - * 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. - */ - -'use server'; - -import { - generateSessionId, - EmbeddedSignInFlowStatus, - EmbeddedSignInFlowHandleRequestPayload, - EmbeddedFlowExecuteRequestConfig, - EmbeddedSignInFlowInitiateResponse, - IdToken, - isEmpty, -} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Server action for signing in a user. - * Handles the embedded sign-in flow and manages session cookies. - * - * @param payload - The embedded sign-in flow payload - * @param request - The embedded flow execute request config - * @returns Promise that resolves when sign-in is complete - */ -const signInAction = async ( - payload?: EmbeddedSignInFlowHandleRequestPayload, - request?: EmbeddedFlowExecuteRequestConfig, - instanceId: number = 0, -): Promise<{ - data?: - | { - afterSignInUrl?: string; - signInUrl?: string; - } - | EmbeddedSignInFlowInitiateResponse; - error?: string; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const cookieStore: ReadonlyRequestCookies = await cookies(); - - let sessionId: string | undefined; - - const existingSessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; - - if (existingSessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken); - sessionId = sessionPayload.sessionId; - } catch { - // Invalid session token, will create new temp session - } - } - - if (!sessionId) { - const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; - - if (tempSessionToken) { - try { - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); - sessionId = tempSession.sessionId; - } catch { - // Invalid temp session, will create new one - } - } - } - - if (!sessionId) { - sessionId = generateSessionId(); - - const tempSessionToken: string = await SessionManager.createTempSession(sessionId); - - cookieStore.set( - SessionManager.getTempSessionCookieName(instanceId), - tempSessionToken, - SessionManager.getTempSessionCookieOptions(), - ); - } - - // If no payload provided, redirect to sign-in URL for redirect-based sign-in. - if (!payload || isEmpty(payload)) { - const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId); - return {data: {signInUrl: String(defaultSignInUrl)}, success: true}; - } - - // Handle embedded sign-in flow - const response: any = await client.signIn(payload, request!, sessionId); - - if (response.flowStatus === EmbeddedSignInFlowStatus.SuccessCompleted) { - const signInResult: Record = await client.signIn( - { - code: response?.authData?.code, - session_state: response?.authData?.session_state, - state: response?.authData?.state, - } as any, - {}, - sessionId, - ); - - if (signInResult) { - const idToken: IdToken = await client.getDecodedIdToken(sessionId); - const userIdFromToken: string = (idToken['sub'] || signInResult['sub'] || sessionId) as string; - const {accessToken}: {accessToken: string} = signInResult as {accessToken: string}; - const scopes: string = signInResult['scope'] as string; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken, - sessionId as string, - scopes, - organizationId, - ); - - cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); - - cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); - } - - const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); - return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true}; - } - - return {data: response as EmbeddedSignInFlowInitiateResponse, success: true}; - } catch (error) { - // eslint-disable-next-line no-console - console.error('[signInAction] Error during sign-in:', error); - return {error: String(error), success: false}; - } -}; - -export default signInAction; +/** + * 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. + */ + +'use server'; + +import { + generateSessionId, + EmbeddedSignInFlowStatus, + EmbeddedSignInFlowHandleRequestPayload, + EmbeddedFlowExecuteRequestConfig, + EmbeddedSignInFlowInitiateResponse, + IdToken, + isEmpty, +} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Server action for signing in a user. + * Handles the embedded sign-in flow and manages session cookies. + * + * @param payload - The embedded sign-in flow payload + * @param request - The embedded flow execute request config + * @returns Promise that resolves when sign-in is complete + */ +const signInAction = async ( + instanceId: number = 0, + payload?: EmbeddedSignInFlowHandleRequestPayload, + request?: EmbeddedFlowExecuteRequestConfig, +): Promise<{ + data?: + | { + afterSignInUrl?: string; + signInUrl?: string; + } + | EmbeddedSignInFlowInitiateResponse; + error?: string; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const cookieStore: ReadonlyRequestCookies = await cookies(); + + let sessionId: string | undefined; + + const existingSessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (existingSessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken); + sessionId = sessionPayload.sessionId; + } catch { + // Invalid session token, will create new temp session + } + } + + if (!sessionId) { + const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + + if (tempSessionToken) { + try { + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken); + sessionId = tempSession.sessionId; + } catch { + // Invalid temp session, will create new one + } + } + } + + if (!sessionId) { + sessionId = generateSessionId(); + + const tempSessionToken: string = await SessionManager.createTempSession(sessionId); + + cookieStore.set( + SessionManager.getTempSessionCookieName(instanceId), + tempSessionToken, + SessionManager.getTempSessionCookieOptions(), + ); + } + + // If no payload provided, redirect to sign-in URL for redirect-based sign-in. + if (!payload || isEmpty(payload)) { + const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId); + return {data: {signInUrl: String(defaultSignInUrl)}, success: true}; + } + + // Handle embedded sign-in flow + const response: any = await client.signIn(payload, request!, sessionId); + + if (response.flowStatus === EmbeddedSignInFlowStatus.SuccessCompleted) { + const signInResult: Record = await client.signIn( + { + code: response?.authData?.code, + session_state: response?.authData?.session_state, + state: response?.authData?.state, + } as any, + {}, + sessionId, + ); + + if (signInResult) { + const idToken: IdToken = await client.getDecodedIdToken(sessionId); + const userIdFromToken: string = (idToken['sub'] || signInResult['sub'] || sessionId) as string; + const {accessToken}: {accessToken: string} = signInResult as {accessToken: string}; + const scopes: string = signInResult['scope'] as string; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken, + sessionId as string, + scopes, + organizationId, + ); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + } + + const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); + return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true}; + } + + return {data: response as EmbeddedSignInFlowInitiateResponse, success: true}; + } catch (error) { + // eslint-disable-next-line no-console + console.error('[signInAction] Error during sign-in:', error); + return {error: String(error), success: false}; + } +}; + +export default signInAction; diff --git a/packages/nextjs/src/server/actions/signUpAction.ts b/packages/nextjs/src/server/actions/signUpAction.ts index 2a0c28dee..4e208a362 100644 --- a/packages/nextjs/src/server/actions/signUpAction.ts +++ b/packages/nextjs/src/server/actions/signUpAction.ts @@ -1,69 +1,69 @@ -/** - * 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. - */ - -'use server'; - -import {EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteResponse, EmbeddedFlowStatus} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action for signing in a user. - * Handles the embedded sign-in flow and manages session cookies. - * - * @param payload - The embedded sign-in flow payload - * @param request - The embedded flow execute request config - * @returns Promise that resolves when sign-in is complete - */ -const signUpAction = async ( - payload?: EmbeddedFlowExecuteRequestPayload, - instanceId: number = 0, -): Promise<{ - data?: - | { - afterSignUpUrl?: string; - signUpUrl?: string; - } - | EmbeddedFlowExecuteResponse; - error?: string; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - - // If no payload provided, redirect to sign-in URL for redirect-based sign-in. - // If there's a payload, handle the embedded sign-in flow. - if (!payload) { - const defaultSignUpUrl: string = ''; - - return {data: {signUpUrl: String(defaultSignUpUrl)}, success: true}; - } - const response: any = await client.signUp(payload); - - if (response.flowStatus === EmbeddedFlowStatus.Complete) { - const afterSignUpUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); - - return {data: {afterSignUpUrl: String(afterSignUpUrl)}, success: true}; - } - - return {data: response as EmbeddedFlowExecuteResponse, success: true}; - } catch (error) { - return {error: String(error), success: false}; - } -}; - -export default signUpAction; +/** + * 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. + */ + +'use server'; + +import {EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteResponse, EmbeddedFlowStatus} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action for signing in a user. + * Handles the embedded sign-in flow and manages session cookies. + * + * @param payload - The embedded sign-in flow payload + * @param request - The embedded flow execute request config + * @returns Promise that resolves when sign-in is complete + */ +const signUpAction = async ( + instanceId: number = 0, + payload?: EmbeddedFlowExecuteRequestPayload, +): Promise<{ + data?: + | { + afterSignUpUrl?: string; + signUpUrl?: string; + } + | EmbeddedFlowExecuteResponse; + error?: string; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + // If no payload provided, redirect to sign-in URL for redirect-based sign-in. + // If there's a payload, handle the embedded sign-in flow. + if (!payload) { + const defaultSignUpUrl: string = ''; + + return {data: {signUpUrl: String(defaultSignUpUrl)}, success: true}; + } + const response: any = await client.signUp(payload); + + if (response.flowStatus === EmbeddedFlowStatus.Complete) { + const afterSignUpUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); + + return {data: {afterSignUpUrl: String(afterSignUpUrl)}, success: true}; + } + + return {data: response as EmbeddedFlowExecuteResponse, success: true}; + } catch (error) { + return {error: String(error), success: false}; + } +}; + +export default signUpAction; diff --git a/packages/nextjs/src/server/actions/switchOrganization.ts b/packages/nextjs/src/server/actions/switchOrganization.ts index d77ef6c5d..fa2d0e700 100644 --- a/packages/nextjs/src/server/actions/switchOrganization.ts +++ b/packages/nextjs/src/server/actions/switchOrganization.ts @@ -1,83 +1,83 @@ -/** - * 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. - */ - -'use server'; - -import {Organization, AsgardeoAPIError, IdToken, TokenResponse} from '@asgardeo/node'; -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action to switch organization. - */ -const switchOrganization = async ( - organization: Organization, - sessionId: string | undefined, - instanceId: number = 0, -): Promise => { - try { - const cookieStore: ReadonlyRequestCookies = await cookies(); - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const resolvedSessionId: string = sessionId ?? ((await getSessionId(instanceId)) as string); - const response: TokenResponse | Response = await client.switchOrganization(organization, resolvedSessionId); - - // After switching organization, we need to refresh the page to get updated session data - // This is because server components don't maintain state between function calls - const {revalidatePath} = await import('next/cache'); - - // Revalidate the current path to refresh the component with new data - revalidatePath('/'); - - if (response) { - const idToken: IdToken = await client.getDecodedIdToken(resolvedSessionId, (response as TokenResponse).idToken); - const userIdFromToken: string = idToken['sub'] as string; - const {accessToken}: {accessToken: string} = response as TokenResponse; - const scopes: string = (response as TokenResponse).scope; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken as string, - resolvedSessionId as string, - scopes, - organizationId, - ); - - logger.debug('[switchOrganization] Session token created successfully.'); - - cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); - } - - return response; - } catch (error) { - throw new AsgardeoAPIError( - `Failed to switch the organizations: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, - 'switchOrganization-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default switchOrganization; +/** + * 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. + */ + +'use server'; + +import {Organization, AsgardeoAPIError, IdToken, TokenResponse} from '@asgardeo/node'; +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action to switch organization. + */ +const switchOrganization = async ( + instanceId: number = 0, + organization: Organization, + sessionId: string | undefined, +): Promise => { + try { + const cookieStore: ReadonlyRequestCookies = await cookies(); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const resolvedSessionId: string = sessionId ?? ((await getSessionId(instanceId)) as string); + const response: TokenResponse | Response = await client.switchOrganization(organization, resolvedSessionId); + + // After switching organization, we need to refresh the page to get updated session data + // This is because server components don't maintain state between function calls + const {revalidatePath} = await import('next/cache'); + + // Revalidate the current path to refresh the component with new data + revalidatePath('/'); + + if (response) { + const idToken: IdToken = await client.getDecodedIdToken(resolvedSessionId, (response as TokenResponse).idToken); + const userIdFromToken: string = idToken['sub'] as string; + const {accessToken}: {accessToken: string} = response as TokenResponse; + const scopes: string = (response as TokenResponse).scope; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + + const sessionToken: string = await SessionManager.createSessionToken( + accessToken, + userIdFromToken as string, + resolvedSessionId as string, + scopes, + organizationId, + ); + + logger.debug('[switchOrganization] Session token created successfully.'); + + cookieStore.set(SessionManager.getSessionCookieName(instanceId), sessionToken, SessionManager.getSessionCookieOptions()); + } + + return response; + } catch (error) { + throw new AsgardeoAPIError( + `Failed to switch the organizations: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'switchOrganization-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default switchOrganization; diff --git a/packages/nextjs/src/server/actions/updateUserProfileAction.ts b/packages/nextjs/src/server/actions/updateUserProfileAction.ts index f2124ccc7..bdde00135 100644 --- a/packages/nextjs/src/server/actions/updateUserProfileAction.ts +++ b/packages/nextjs/src/server/actions/updateUserProfileAction.ts @@ -1,48 +1,48 @@ -/** - * 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. - */ - -'use server'; - -import {UpdateMeProfileConfig, User} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const updateUserProfileAction = async ( - payload: UpdateMeProfileConfig, - sessionId?: string, - instanceId: number = 0, -): Promise<{data: {user: User}; error: string; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const user: User = await client.updateUserProfile(payload, sessionId); - return {data: {user}, error: '', success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: `Failed to get user profile: ${error instanceof Error ? error.message : String(error)}`, - success: false, - }; - } -}; - -export default updateUserProfileAction; +/** + * 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. + */ + +'use server'; + +import {UpdateMeProfileConfig, User} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const updateUserProfileAction = async ( + instanceId: number = 0, + payload: UpdateMeProfileConfig, + sessionId?: string, +): Promise<{data: {user: User}; error: string; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const user: User = await client.updateUserProfile(payload, sessionId); + return {data: {user}, error: '', success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: `Failed to get user profile: ${error instanceof Error ? error.message : String(error)}`, + success: false, + }; + } +}; + +export default updateUserProfileAction; diff --git a/packages/nextjs/src/server/asgardeo.ts b/packages/nextjs/src/server/asgardeo.ts index 348dca150..ea35f5af4 100644 --- a/packages/nextjs/src/server/asgardeo.ts +++ b/packages/nextjs/src/server/asgardeo.ts @@ -21,29 +21,29 @@ import getSessionIdAction from './actions/getSessionId'; import AsgardeoNextClient from '../AsgardeoNextClient'; import {AsgardeoNextConfig} from '../models/config'; -const asgardeo = async (): Promise<{ +const asgardeo = async (instanceId: number = 0): Promise<{ exchangeToken: (config: TokenExchangeRequestConfig, sessionId: string) => Promise; getAccessToken: (sessionId: string) => Promise; getSessionId: () => Promise; reInitialize: (config: Partial) => Promise; }> => { const getAccessToken = async (sessionId: string): Promise => { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); return client.getAccessToken(sessionId); }; - const getSessionId = async (): Promise => getSessionIdAction(); + const getSessionId = async (): Promise => getSessionIdAction(instanceId); const exchangeToken = async ( config: TokenExchangeRequestConfig, sessionId: string, ): Promise => { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); return client.exchangeToken(config, sessionId); }; const reInitialize = async (config: Partial): Promise => { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); return client.reInitialize(config); }; diff --git a/packages/nextjs/src/server/middleware/asgardeoMiddleware.ts b/packages/nextjs/src/server/middleware/asgardeoMiddleware.ts index da9e33bcb..c540c2a41 100644 --- a/packages/nextjs/src/server/middleware/asgardeoMiddleware.ts +++ b/packages/nextjs/src/server/middleware/asgardeoMiddleware.ts @@ -25,13 +25,15 @@ import { getSessionIdFromRequest, } from '../../utils/sessionUtils'; -export type AsgardeoMiddlewareOptions = Partial; +export type AsgardeoMiddlewareOptions = Partial & {instanceId?: number}; export type AsgardeoMiddlewareContext = { /** Get the session payload from JWT session if available */ getSession: () => Promise; /** Get the session ID from the current request */ getSessionId: () => string | undefined; + /** The instance ID for this middleware context */ + instanceId: number; /** Check if the current request has a valid Asgardeo session */ isSignedIn: () => boolean; /** @@ -57,9 +59,9 @@ type AsgardeoMiddlewareHandler = ( * @param request - The Next.js request object * @returns True if a valid session exists, false otherwise */ -const hasValidSession = async (request: NextRequest): Promise => { +const hasValidSession = async (request: NextRequest, instanceId: number = 0): Promise => { try { - return await hasValidJWTSession(request); + return await hasValidJWTSession(request, instanceId); } catch { return Promise.resolve(false); } @@ -72,8 +74,8 @@ const hasValidSession = async (request: NextRequest): Promise => { * @param request - The Next.js request object * @returns The session ID if it exists, undefined otherwise */ -const getSessionIdFromRequestMiddleware = async (request: NextRequest): Promise => - getSessionIdFromRequest(request); +const getSessionIdFromRequestMiddleware = async (request: NextRequest, instanceId: number = 0): Promise => + getSessionIdFromRequest(request, instanceId); /** * Asgardeo middleware that integrates authentication into your Next.js application. @@ -138,6 +140,7 @@ const asgardeoMiddleware = ): ((request: NextRequest) => Promise) => async (request: NextRequest): Promise => { const resolvedOptions: AsgardeoMiddlewareOptions = typeof options === 'function' ? options(request) : options || {}; + const instanceId: number = resolvedOptions.instanceId ?? 0; const url: URL = new URL(request.url); const hasCallbackParams: boolean = url.searchParams.has('code') && url.searchParams.has('state'); @@ -150,7 +153,7 @@ const asgardeoMiddleware = if (!hasError) { // Validate that there's a temporary session that initiated this OAuth flow const tempSessionToken: string | undefined = request.cookies.get( - SessionManager.getTempSessionCookieName(), + SessionManager.getTempSessionCookieName(instanceId), )?.value; if (tempSessionToken) { try { @@ -165,18 +168,19 @@ const asgardeoMiddleware = } } - const sessionId: string | undefined = await getSessionIdFromRequestMiddleware(request); - const isAuthenticated: boolean = await hasValidSession(request); + const sessionId: string | undefined = await getSessionIdFromRequestMiddleware(request, instanceId); + const isAuthenticated: boolean = await hasValidSession(request, instanceId); const asgardeo: AsgardeoMiddlewareContext = { getSession: async (): Promise => { try { - return await getSessionFromRequest(request); + return await getSessionFromRequest(request, instanceId); } catch { return undefined; } }, getSessionId: (): string | undefined => sessionId, + instanceId, isSignedIn: (): boolean => isAuthenticated, // eslint-disable-next-line @typescript-eslint/no-unused-vars protectRoute: async (routeOptions?: {redirect?: string}): Promise => { From 7bd67bd55ba520b7db094ed342961c1f1a880be1 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Wed, 11 Mar 2026 12:37:52 +0530 Subject: [PATCH 5/7] Change crlf to lf --- .../src/server/actions/getAccessToken.ts | 96 +++++----- .../actions/getCurrentOrganizationAction.ts | 100 +++++------ .../src/server/actions/getMyOrganizations.ts | 164 +++++++++--------- .../server/actions/getOrganizationAction.ts | 102 +++++------ .../nextjs/src/server/actions/getSessionId.ts | 98 +++++------ .../src/server/actions/getSessionPayload.ts | 92 +++++----- .../src/server/actions/getUserAction.ts | 82 ++++----- .../server/actions/getUserProfileAction.ts | 102 +++++------ .../nextjs/src/server/actions/isSignedIn.ts | 142 +++++++-------- .../src/server/actions/signOutAction.ts | 164 +++++++++--------- 10 files changed, 571 insertions(+), 571 deletions(-) diff --git a/packages/nextjs/src/server/actions/getAccessToken.ts b/packages/nextjs/src/server/actions/getAccessToken.ts index 45712c69b..e4f009688 100644 --- a/packages/nextjs/src/server/actions/getAccessToken.ts +++ b/packages/nextjs/src/server/actions/getAccessToken.ts @@ -1,48 +1,48 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the access token from the session cookie. - * - * @returns The access token if it exists, undefined otherwise - */ -const getAccessToken = async (instanceId: number = 0): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; - - if (sessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); - - return sessionPayload['accessToken'] as string; - } catch (error) { - return undefined; - } - } - - return undefined; -}; - -export default getAccessToken; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the access token from the session cookie. + * + * @returns The access token if it exists, undefined otherwise + */ +const getAccessToken = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (sessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); + + return sessionPayload['accessToken'] as string; + } catch (error) { + return undefined; + } + } + + return undefined; +}; + +export default getAccessToken; diff --git a/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts b/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts index 4aaff4b9f..172d382b6 100644 --- a/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts +++ b/packages/nextjs/src/server/actions/getCurrentOrganizationAction.ts @@ -1,50 +1,50 @@ -/** - * 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. - */ - -'use server'; - -import {Organization} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const getCurrentOrganizationAction = async ( - sessionId: string, - instanceId: number = 0, -): Promise<{ - data: {organization?: Organization; user?: Record}; - error: string | null; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const organization: Organization = (await client.getCurrentOrganization(sessionId)) as Organization; - return {data: {organization}, error: null, success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: 'Failed to get the current organization', - success: false, - }; - } -}; - -export default getCurrentOrganizationAction; +/** + * 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. + */ + +'use server'; + +import {Organization} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const getCurrentOrganizationAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{ + data: {organization?: Organization; user?: Record}; + error: string | null; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const organization: Organization = (await client.getCurrentOrganization(sessionId)) as Organization; + return {data: {organization}, error: null, success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: 'Failed to get the current organization', + success: false, + }; + } +}; + +export default getCurrentOrganizationAction; diff --git a/packages/nextjs/src/server/actions/getMyOrganizations.ts b/packages/nextjs/src/server/actions/getMyOrganizations.ts index 13fb988a6..ef74a704c 100644 --- a/packages/nextjs/src/server/actions/getMyOrganizations.ts +++ b/packages/nextjs/src/server/actions/getMyOrganizations.ts @@ -1,82 +1,82 @@ -/** - * 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. - */ - -'use server'; - -import {AsgardeoAPIError, Organization} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get organizations. - */ -const getMyOrganizations = async (options?: any, sessionId?: string | undefined, instanceId: number = 0): Promise => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - - // Get session ID if not provided - let resolvedSessionId: string | undefined = sessionId; - if (!resolvedSessionId) { - // Import getSessionId locally to avoid circular dependencies - const {default: getSessionId} = await import('./getSessionId'); - resolvedSessionId = await getSessionId(instanceId); - } - - if (!resolvedSessionId) { - throw new AsgardeoAPIError( - 'No session ID available for fetching organizations', - 'getMyOrganizations-SessionError-001', - 'nextjs', - 401, - ); - } - - // Check if user is signed in by trying to get access token - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - - if (!accessToken) { - throw new AsgardeoAPIError( - 'No access token available - user is not signed in', - 'getMyOrganizations-NoAccessToken-001', - 'nextjs', - 401, - ); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('[getMyOrganizations] Failed to get access token:', error); - throw new AsgardeoAPIError( - 'User is not signed in - access token retrieval failed', - 'getMyOrganizations-NotSignedIn-001', - 'nextjs', - 401, - ); - } - - return await client.getMyOrganizations(options, resolvedSessionId); - } catch (error) { - throw new AsgardeoAPIError( - `Failed to get the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, - 'getMyOrganizations-ServerActionError-001', - 'nextjs', - error instanceof AsgardeoAPIError ? error.statusCode : undefined, - ); - } -}; - -export default getMyOrganizations; +/** + * 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. + */ + +'use server'; + +import {AsgardeoAPIError, Organization} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get organizations. + */ +const getMyOrganizations = async (options?: any, sessionId?: string | undefined, instanceId: number = 0): Promise => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + // Get session ID if not provided + let resolvedSessionId: string | undefined = sessionId; + if (!resolvedSessionId) { + // Import getSessionId locally to avoid circular dependencies + const {default: getSessionId} = await import('./getSessionId'); + resolvedSessionId = await getSessionId(instanceId); + } + + if (!resolvedSessionId) { + throw new AsgardeoAPIError( + 'No session ID available for fetching organizations', + 'getMyOrganizations-SessionError-001', + 'nextjs', + 401, + ); + } + + // Check if user is signed in by trying to get access token + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + + if (!accessToken) { + throw new AsgardeoAPIError( + 'No access token available - user is not signed in', + 'getMyOrganizations-NoAccessToken-001', + 'nextjs', + 401, + ); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('[getMyOrganizations] Failed to get access token:', error); + throw new AsgardeoAPIError( + 'User is not signed in - access token retrieval failed', + 'getMyOrganizations-NotSignedIn-001', + 'nextjs', + 401, + ); + } + + return await client.getMyOrganizations(options, resolvedSessionId); + } catch (error) { + throw new AsgardeoAPIError( + `Failed to get the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, + 'getMyOrganizations-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); + } +}; + +export default getMyOrganizations; diff --git a/packages/nextjs/src/server/actions/getOrganizationAction.ts b/packages/nextjs/src/server/actions/getOrganizationAction.ts index 9e0383617..ebb955665 100644 --- a/packages/nextjs/src/server/actions/getOrganizationAction.ts +++ b/packages/nextjs/src/server/actions/getOrganizationAction.ts @@ -1,51 +1,51 @@ -/** - * 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. - */ - -'use server'; - -import {OrganizationDetails} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to create an organization. - */ -const getOrganizationAction = async ( - organizationId: string, - sessionId: string, - instanceId: number = 0, -): Promise<{ - data: {organization?: OrganizationDetails; user?: Record}; - error: string | null; - success: boolean; -}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const organization: OrganizationDetails = await client.getOrganization(organizationId, sessionId); - return {data: {organization}, error: null, success: true}; - } catch (error) { - return { - data: { - user: {}, - }, - error: 'Failed to get organization', - success: false, - }; - } -}; - -export default getOrganizationAction; +/** + * 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. + */ + +'use server'; + +import {OrganizationDetails} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to create an organization. + */ +const getOrganizationAction = async ( + organizationId: string, + sessionId: string, + instanceId: number = 0, +): Promise<{ + data: {organization?: OrganizationDetails; user?: Record}; + error: string | null; + success: boolean; +}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const organization: OrganizationDetails = await client.getOrganization(organizationId, sessionId); + return {data: {organization}, error: null, success: true}; + } catch (error) { + return { + data: { + user: {}, + }, + error: 'Failed to get organization', + success: false, + }; + } +}; + +export default getOrganizationAction; diff --git a/packages/nextjs/src/server/actions/getSessionId.ts b/packages/nextjs/src/server/actions/getSessionId.ts index f1843657c..5bd55597f 100644 --- a/packages/nextjs/src/server/actions/getSessionId.ts +++ b/packages/nextjs/src/server/actions/getSessionId.ts @@ -1,49 +1,49 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the session ID from cookies. - * Tries JWT session first, then falls back to legacy session ID. - * - * @returns The session ID if it exists, undefined otherwise - */ -const getSessionId = async (instanceId: number = 0): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; - - if (sessionToken) { - try { - const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); - - return sessionPayload.sessionId; - } catch (error) { - return undefined; - } - } - - return undefined; -}; - -export default getSessionId; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the session ID from cookies. + * Tries JWT session first, then falls back to legacy session ID. + * + * @returns The session ID if it exists, undefined otherwise + */ +const getSessionId = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + + if (sessionToken) { + try { + const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(sessionToken); + + return sessionPayload.sessionId; + } catch (error) { + return undefined; + } + } + + return undefined; +}; + +export default getSessionId; diff --git a/packages/nextjs/src/server/actions/getSessionPayload.ts b/packages/nextjs/src/server/actions/getSessionPayload.ts index 55423c74c..f8eec6221 100644 --- a/packages/nextjs/src/server/actions/getSessionPayload.ts +++ b/packages/nextjs/src/server/actions/getSessionPayload.ts @@ -1,46 +1,46 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Get the session payload from JWT session cookie. - * This includes user ID, session ID, scopes, and organization ID. - * - * @returns The session payload if valid JWT session exists, undefined otherwise - */ -const getSessionPayload = async (instanceId: number = 0): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; - if (!sessionToken) { - return undefined; - } - - try { - return await SessionManager.verifySessionToken(sessionToken); - } catch { - return undefined; - } -}; - -export default getSessionPayload; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import SessionManager, {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Get the session payload from JWT session cookie. + * This includes user ID, session ID, scopes, and organization ID. + * + * @returns The session payload if valid JWT session exists, undefined otherwise + */ +const getSessionPayload = async (instanceId: number = 0): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + const sessionToken: string | undefined = cookieStore.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return undefined; + } + + try { + return await SessionManager.verifySessionToken(sessionToken); + } catch { + return undefined; + } +}; + +export default getSessionPayload; diff --git a/packages/nextjs/src/server/actions/getUserAction.ts b/packages/nextjs/src/server/actions/getUserAction.ts index db328fdc9..97664cca4 100644 --- a/packages/nextjs/src/server/actions/getUserAction.ts +++ b/packages/nextjs/src/server/actions/getUserAction.ts @@ -1,41 +1,41 @@ -/** - * 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. - */ - -'use server'; - -import {User} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const getUserAction = async ( - sessionId: string, - instanceId: number = 0, -): Promise<{data: {user: User | null}; error: string | null; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const user: User = await client.getUser(sessionId); - return {data: {user}, error: null, success: true}; - } catch (error) { - return {data: {user: null}, error: 'Failed to get user', success: false}; - } -}; - -export default getUserAction; +/** + * 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. + */ + +'use server'; + +import {User} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const getUserAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{data: {user: User | null}; error: string | null; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const user: User = await client.getUser(sessionId); + return {data: {user}, error: null, success: true}; + } catch (error) { + return {data: {user: null}, error: 'Failed to get user', success: false}; + } +}; + +export default getUserAction; diff --git a/packages/nextjs/src/server/actions/getUserProfileAction.ts b/packages/nextjs/src/server/actions/getUserProfileAction.ts index b2808ae20..5d6180455 100644 --- a/packages/nextjs/src/server/actions/getUserProfileAction.ts +++ b/packages/nextjs/src/server/actions/getUserProfileAction.ts @@ -1,51 +1,51 @@ -/** - * 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. - */ - -'use server'; - -import {UserProfile} from '@asgardeo/node'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; - -/** - * Server action to get the current user. - * Returns the user profile if signed in. - */ -const getUserProfileAction = async ( - sessionId: string, - instanceId: number = 0, -): Promise<{data: {userProfile: UserProfile}; error: string | null; success: boolean}> => { - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const updatedProfile: UserProfile = await client.getUserProfile(sessionId); - return {data: {userProfile: updatedProfile}, error: null, success: true}; - } catch (error) { - return { - data: { - userProfile: { - flattenedProfile: {}, - profile: {}, - schemas: [], - }, - }, - error: 'Failed to get user profile', - success: false, - }; - } -}; - -export default getUserProfileAction; +/** + * 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. + */ + +'use server'; + +import {UserProfile} from '@asgardeo/node'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; + +/** + * Server action to get the current user. + * Returns the user profile if signed in. + */ +const getUserProfileAction = async ( + sessionId: string, + instanceId: number = 0, +): Promise<{data: {userProfile: UserProfile}; error: string | null; success: boolean}> => { + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const updatedProfile: UserProfile = await client.getUserProfile(sessionId); + return {data: {userProfile: updatedProfile}, error: null, success: true}; + } catch (error) { + return { + data: { + userProfile: { + flattenedProfile: {}, + profile: {}, + schemas: [], + }, + }, + error: 'Failed to get user profile', + success: false, + }; + } +}; + +export default getUserProfileAction; diff --git a/packages/nextjs/src/server/actions/isSignedIn.ts b/packages/nextjs/src/server/actions/isSignedIn.ts index 670c4d2ea..9b2d9db3f 100644 --- a/packages/nextjs/src/server/actions/isSignedIn.ts +++ b/packages/nextjs/src/server/actions/isSignedIn.ts @@ -1,71 +1,71 @@ -/** - * 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. - */ - -'use server'; - -import getSessionId from './getSessionId'; -import getSessionPayload from './getSessionPayload'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import {SessionTokenPayload} from '../../utils/SessionManager'; - -/** - * Check if the user is currently signed in. - * First tries JWT session validation, then falls back to legacy session check. - * - * @param sessionId - Optional session ID to check (if not provided, gets from cookies) - * @returns True if user is signed in, false otherwise - */ -const isSignedIn = async (sessionId?: string, instanceId: number = 0): Promise => { - try { - const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(instanceId); - - if (sessionPayload) { - const resolvedSessionId: string = sessionPayload.sessionId; - - if (resolvedSessionId) { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - return !!accessToken; - } catch (error) { - return false; - } - } - } - - const resolvedSessionId: string | undefined = sessionId || (await getSessionId(instanceId)); - - if (!resolvedSessionId) { - return false; - } - - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - - try { - const accessToken: string = await client.getAccessToken(resolvedSessionId); - - return !!accessToken; - } catch (error) { - return false; - } - } catch { - return false; - } -}; - -export default isSignedIn; +/** + * 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. + */ + +'use server'; + +import getSessionId from './getSessionId'; +import getSessionPayload from './getSessionPayload'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import {SessionTokenPayload} from '../../utils/SessionManager'; + +/** + * Check if the user is currently signed in. + * First tries JWT session validation, then falls back to legacy session check. + * + * @param sessionId - Optional session ID to check (if not provided, gets from cookies) + * @returns True if user is signed in, false otherwise + */ +const isSignedIn = async (sessionId?: string, instanceId: number = 0): Promise => { + try { + const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(instanceId); + + if (sessionPayload) { + const resolvedSessionId: string = sessionPayload.sessionId; + + if (resolvedSessionId) { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + return !!accessToken; + } catch (error) { + return false; + } + } + } + + const resolvedSessionId: string | undefined = sessionId || (await getSessionId(instanceId)); + + if (!resolvedSessionId) { + return false; + } + + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + + try { + const accessToken: string = await client.getAccessToken(resolvedSessionId); + + return !!accessToken; + } catch (error) { + return false; + } + } catch { + return false; + } +}; + +export default isSignedIn; diff --git a/packages/nextjs/src/server/actions/signOutAction.ts b/packages/nextjs/src/server/actions/signOutAction.ts index 641b1c873..e323d2537 100644 --- a/packages/nextjs/src/server/actions/signOutAction.ts +++ b/packages/nextjs/src/server/actions/signOutAction.ts @@ -1,82 +1,82 @@ -/** - * 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. - */ - -'use server'; - -import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; -import {cookies} from 'next/headers'; -import getSessionId from './getSessionId'; -import AsgardeoNextClient from '../../AsgardeoNextClient'; -import logger from '../../utils/logger'; -import SessionManager from '../../utils/SessionManager'; - -/** - * Server action for signing out a user. - * Clears both JWT and legacy session cookies. - * - * @returns Promise that resolves with success status and optional after sign-out URL - */ -const signOutAction = async (instanceId: number = 0): Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}> => { - logger.debug('[signOutAction] Initiating sign out process from the server action.'); - - const clearSessionCookies = async (): Promise => { - const cookieStore: ReadonlyRequestCookies = await cookies(); - - cookieStore.delete(SessionManager.getSessionCookieName(instanceId)); - cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); - }; - - try { - const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); - const sessionId: string | undefined = await getSessionId(instanceId); - - let afterSignOutUrl: string = '/'; - - if (sessionId) { - logger.debug('[signOutAction] Session ID found, invoking the `signOut` to obtain the `afterSignOutUrl`.'); - - afterSignOutUrl = await client.signOut({}, sessionId); - } - - await clearSessionCookies(); - - return {data: {afterSignOutUrl}, success: true}; - } catch (error) { - logger.error('[signOutAction] Error during sign out from the server action:', error); - - logger.debug('[signOutAction] Clearing session cookies due to error as a fallback.'); - - await clearSessionCookies(); - - let errorMessage: unknown; - if (typeof error === 'string') { - errorMessage = error; - } else if (error instanceof Error) { - errorMessage = error.message; - } else { - errorMessage = JSON.stringify(error); - } - - return { - error: errorMessage, - success: false, - }; - } -}; - -export default signOutAction; +/** + * 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. + */ + +'use server'; + +import {ReadonlyRequestCookies} from 'next/dist/server/web/spec-extension/adapters/request-cookies'; +import {cookies} from 'next/headers'; +import getSessionId from './getSessionId'; +import AsgardeoNextClient from '../../AsgardeoNextClient'; +import logger from '../../utils/logger'; +import SessionManager from '../../utils/SessionManager'; + +/** + * Server action for signing out a user. + * Clears both JWT and legacy session cookies. + * + * @returns Promise that resolves with success status and optional after sign-out URL + */ +const signOutAction = async (instanceId: number = 0): Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}> => { + logger.debug('[signOutAction] Initiating sign out process from the server action.'); + + const clearSessionCookies = async (): Promise => { + const cookieStore: ReadonlyRequestCookies = await cookies(); + + cookieStore.delete(SessionManager.getSessionCookieName(instanceId)); + cookieStore.delete(SessionManager.getTempSessionCookieName(instanceId)); + }; + + try { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(instanceId); + const sessionId: string | undefined = await getSessionId(instanceId); + + let afterSignOutUrl: string = '/'; + + if (sessionId) { + logger.debug('[signOutAction] Session ID found, invoking the `signOut` to obtain the `afterSignOutUrl`.'); + + afterSignOutUrl = await client.signOut({}, sessionId); + } + + await clearSessionCookies(); + + return {data: {afterSignOutUrl}, success: true}; + } catch (error) { + logger.error('[signOutAction] Error during sign out from the server action:', error); + + logger.debug('[signOutAction] Clearing session cookies due to error as a fallback.'); + + await clearSessionCookies(); + + let errorMessage: unknown; + if (typeof error === 'string') { + errorMessage = error; + } else if (error instanceof Error) { + errorMessage = error.message; + } else { + errorMessage = JSON.stringify(error); + } + + return { + error: errorMessage, + success: false, + }; + } +}; + +export default signOutAction; From 5025828e19e70e8b2cc464c73a4ce8a1ac061a95 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Wed, 11 Mar 2026 12:39:34 +0530 Subject: [PATCH 6/7] Change crlf to lf --- packages/nextjs/src/utils/SessionManager.ts | 438 ++++++++++---------- packages/nextjs/src/utils/sessionUtils.ts | 204 ++++----- 2 files changed, 321 insertions(+), 321 deletions(-) diff --git a/packages/nextjs/src/utils/SessionManager.ts b/packages/nextjs/src/utils/SessionManager.ts index 9f825071a..494428664 100644 --- a/packages/nextjs/src/utils/SessionManager.ts +++ b/packages/nextjs/src/utils/SessionManager.ts @@ -1,219 +1,219 @@ -/** - * 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 {AsgardeoRuntimeError, CookieConfig} from '@asgardeo/node'; -import {SignJWT, jwtVerify, JWTPayload} from 'jose'; - -/** - * Session token payload interface - */ -export interface SessionTokenPayload extends JWTPayload { - /** Expiration timestamp */ - exp: number; - /** Issued at timestamp */ - iat: number; - /** Organization ID if applicable */ - organizationId?: string; - /** OAuth scopes */ - scopes: string[]; - /** Session ID */ - sessionId: string; - /** User ID */ - sub: string; -} - -/** - * Session management utility class for JWT-based session cookies - */ -class SessionManager { - private static readonly DEFAULT_EXPIRY_SECONDS: number = 3600; - - /** - * Get the signing secret from environment variable - * Throws error in production if not set - */ - private static getSecret(): Uint8Array { - const secret: string | undefined = process.env['ASGARDEO_SECRET']; - - if (!secret) { - if (process.env['NODE_ENV'] === 'production') { - throw new AsgardeoRuntimeError( - 'ASGARDEO_SECRET environment variable is required in production', - 'session-secret-required', - 'nextjs', - 'Set the ASGARDEO_SECRET environment variable with a secure random string', - ); - } - // Use a default secret for development (not secure) - // eslint-disable-next-line no-console - console.warn('Using default secret for development. Set ASGARDEO_SECRET for production!'); - return new TextEncoder().encode('development-secret-not-for-production'); - } - - return new TextEncoder().encode(secret); - } - - /** - * Create a temporary session cookie for login initiation - */ - static async createTempSession(sessionId: string): Promise { - const secret: Uint8Array = this.getSecret(); - - const jwt: string = await new SignJWT({ - sessionId, - type: 'temp', - }) - .setProtectedHeader({alg: 'HS256'}) - .setIssuedAt() - .setExpirationTime('15m') - .sign(secret); - - return jwt; - } - - /** - * Create a session cookie with user information - */ - static async createSessionToken( - accessToken: string, - userId: string, - sessionId: string, - scopes: string, - organizationId?: string, - expirySeconds: number = this.DEFAULT_EXPIRY_SECONDS, - ): Promise { - const secret: Uint8Array = this.getSecret(); - - const jwt: string = await new SignJWT({ - accessToken, - organizationId, - scopes, - sessionId, - type: 'session', - } as Omit) - .setProtectedHeader({alg: 'HS256'}) - .setSubject(userId) - .setIssuedAt() - .setExpirationTime(Date.now() / 1000 + expirySeconds) - .sign(secret); - - return jwt; - } - - /** - * Verify and decode a session token - */ - static async verifySessionToken(token: string): Promise { - try { - const secret: Uint8Array = this.getSecret(); - const {payload} = await jwtVerify(token, secret); - - return payload as SessionTokenPayload; - } catch (error) { - throw new AsgardeoRuntimeError( - `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`, - 'invalid-session-token', - 'nextjs', - 'Session token verification failed', - ); - } - } - - /** - * Verify and decode a temporary session token - */ - static async verifyTempSession(token: string): Promise<{sessionId: string}> { - try { - const secret: Uint8Array = this.getSecret(); - const {payload} = await jwtVerify(token, secret); - - if (payload['type'] !== 'temp') { - throw new Error('Invalid token type'); - } - - return {sessionId: payload['sessionId'] as string}; - } catch (error) { - throw new AsgardeoRuntimeError( - `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`, - 'invalid-temp-session-token', - 'nextjs', - 'Temporary session token verification failed', - ); - } - } - - /** - * Get session cookie options - */ - static getSessionCookieOptions(): { - httpOnly: boolean; - maxAge: number; - path: string; - sameSite: 'lax'; - secure: boolean; - } { - return { - httpOnly: true, - maxAge: this.DEFAULT_EXPIRY_SECONDS, - path: '/', - sameSite: 'lax' as const, - secure: process.env['NODE_ENV'] === 'production', - }; - } - - /** - * Get temporary session cookie options - */ - static getTempSessionCookieOptions(): { - httpOnly: boolean; - maxAge: number; - path: string; - sameSite: 'lax'; - secure: boolean; - } { - return { - httpOnly: true, - maxAge: 15 * 60, - path: '/', - sameSite: 'lax' as const, - secure: process.env['NODE_ENV'] === 'production', - }; - } - - /** - * Get session cookie name - */ - static getSessionCookieName(instanceId: number = 0): string { - if (instanceId === 0) { - return CookieConfig.SESSION_COOKIE_NAME; - } - return `${CookieConfig.SESSION_COOKIE_NAME}.${instanceId}`; - } - - /** - * Get temporary session cookie name - */ - static getTempSessionCookieName(instanceId: number = 0): string { - if (instanceId === 0) { - return CookieConfig.TEMP_SESSION_COOKIE_NAME; - } - return `${CookieConfig.TEMP_SESSION_COOKIE_NAME}.${instanceId}`; - } -} - -export default SessionManager; +/** + * 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 {AsgardeoRuntimeError, CookieConfig} from '@asgardeo/node'; +import {SignJWT, jwtVerify, JWTPayload} from 'jose'; + +/** + * Session token payload interface + */ +export interface SessionTokenPayload extends JWTPayload { + /** Expiration timestamp */ + exp: number; + /** Issued at timestamp */ + iat: number; + /** Organization ID if applicable */ + organizationId?: string; + /** OAuth scopes */ + scopes: string[]; + /** Session ID */ + sessionId: string; + /** User ID */ + sub: string; +} + +/** + * Session management utility class for JWT-based session cookies + */ +class SessionManager { + private static readonly DEFAULT_EXPIRY_SECONDS: number = 3600; + + /** + * Get the signing secret from environment variable + * Throws error in production if not set + */ + private static getSecret(): Uint8Array { + const secret: string | undefined = process.env['ASGARDEO_SECRET']; + + if (!secret) { + if (process.env['NODE_ENV'] === 'production') { + throw new AsgardeoRuntimeError( + 'ASGARDEO_SECRET environment variable is required in production', + 'session-secret-required', + 'nextjs', + 'Set the ASGARDEO_SECRET environment variable with a secure random string', + ); + } + // Use a default secret for development (not secure) + // eslint-disable-next-line no-console + console.warn('Using default secret for development. Set ASGARDEO_SECRET for production!'); + return new TextEncoder().encode('development-secret-not-for-production'); + } + + return new TextEncoder().encode(secret); + } + + /** + * Create a temporary session cookie for login initiation + */ + static async createTempSession(sessionId: string): Promise { + const secret: Uint8Array = this.getSecret(); + + const jwt: string = await new SignJWT({ + sessionId, + type: 'temp', + }) + .setProtectedHeader({alg: 'HS256'}) + .setIssuedAt() + .setExpirationTime('15m') + .sign(secret); + + return jwt; + } + + /** + * Create a session cookie with user information + */ + static async createSessionToken( + accessToken: string, + userId: string, + sessionId: string, + scopes: string, + organizationId?: string, + expirySeconds: number = this.DEFAULT_EXPIRY_SECONDS, + ): Promise { + const secret: Uint8Array = this.getSecret(); + + const jwt: string = await new SignJWT({ + accessToken, + organizationId, + scopes, + sessionId, + type: 'session', + } as Omit) + .setProtectedHeader({alg: 'HS256'}) + .setSubject(userId) + .setIssuedAt() + .setExpirationTime(Date.now() / 1000 + expirySeconds) + .sign(secret); + + return jwt; + } + + /** + * Verify and decode a session token + */ + static async verifySessionToken(token: string): Promise { + try { + const secret: Uint8Array = this.getSecret(); + const {payload} = await jwtVerify(token, secret); + + return payload as SessionTokenPayload; + } catch (error) { + throw new AsgardeoRuntimeError( + `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'invalid-session-token', + 'nextjs', + 'Session token verification failed', + ); + } + } + + /** + * Verify and decode a temporary session token + */ + static async verifyTempSession(token: string): Promise<{sessionId: string}> { + try { + const secret: Uint8Array = this.getSecret(); + const {payload} = await jwtVerify(token, secret); + + if (payload['type'] !== 'temp') { + throw new Error('Invalid token type'); + } + + return {sessionId: payload['sessionId'] as string}; + } catch (error) { + throw new AsgardeoRuntimeError( + `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`, + 'invalid-temp-session-token', + 'nextjs', + 'Temporary session token verification failed', + ); + } + } + + /** + * Get session cookie options + */ + static getSessionCookieOptions(): { + httpOnly: boolean; + maxAge: number; + path: string; + sameSite: 'lax'; + secure: boolean; + } { + return { + httpOnly: true, + maxAge: this.DEFAULT_EXPIRY_SECONDS, + path: '/', + sameSite: 'lax' as const, + secure: process.env['NODE_ENV'] === 'production', + }; + } + + /** + * Get temporary session cookie options + */ + static getTempSessionCookieOptions(): { + httpOnly: boolean; + maxAge: number; + path: string; + sameSite: 'lax'; + secure: boolean; + } { + return { + httpOnly: true, + maxAge: 15 * 60, + path: '/', + sameSite: 'lax' as const, + secure: process.env['NODE_ENV'] === 'production', + }; + } + + /** + * Get session cookie name + */ + static getSessionCookieName(instanceId: number = 0): string { + if (instanceId === 0) { + return CookieConfig.SESSION_COOKIE_NAME; + } + return `${CookieConfig.SESSION_COOKIE_NAME}.${instanceId}`; + } + + /** + * Get temporary session cookie name + */ + static getTempSessionCookieName(instanceId: number = 0): string { + if (instanceId === 0) { + return CookieConfig.TEMP_SESSION_COOKIE_NAME; + } + return `${CookieConfig.TEMP_SESSION_COOKIE_NAME}.${instanceId}`; + } +} + +export default SessionManager; diff --git a/packages/nextjs/src/utils/sessionUtils.ts b/packages/nextjs/src/utils/sessionUtils.ts index 8d7ea49bf..57eeb6356 100644 --- a/packages/nextjs/src/utils/sessionUtils.ts +++ b/packages/nextjs/src/utils/sessionUtils.ts @@ -1,102 +1,102 @@ -/** - * 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 {NextRequest} from 'next/server'; -import SessionManager, {SessionTokenPayload} from './SessionManager'; - -/** - * Checks if a request has a valid session cookie (JWT). - * This verifies the JWT signature and expiration. - * - * @param request - The Next.js request object - * @returns True if a valid session exists, false otherwise - */ -export const hasValidSession = async (request: NextRequest, instanceId: number = 0): Promise => { - try { - const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; - if (!sessionToken) { - return false; - } - - await SessionManager.verifySessionToken(sessionToken); - return true; - } catch { - return false; - } -}; - -/** - * Gets the session payload from the request cookies. - * This includes user ID, session ID, and scopes. - * - * @param request - The Next.js request object - * @returns The session payload if valid, undefined otherwise - */ -export const getSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { - try { - const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; - if (!sessionToken) { - return undefined; - } - - return await SessionManager.verifySessionToken(sessionToken); - } catch { - return undefined; - } -}; - -/** - * Gets the session ID from the request cookies (legacy support). - * First tries to get from JWT session, then falls back to legacy session ID cookie. - * - * @param request - The Next.js request object - * @returns The session ID if it exists, undefined otherwise - */ -export const getSessionIdFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { - try { - const sessionPayload: SessionTokenPayload | undefined = await getSessionFromRequest(request, instanceId); - - if (sessionPayload) { - return sessionPayload.sessionId; - } - - return await Promise.resolve(undefined); - } catch { - return Promise.resolve(undefined); - } -}; - -/** - * Gets the temporary session ID from request cookies. - * - * @param request - The Next.js request object - * @returns The temporary session ID if valid, undefined otherwise - */ -export const getTempSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { - try { - const tempToken: string | undefined = request.cookies.get(SessionManager.getTempSessionCookieName(instanceId))?.value; - if (!tempToken) { - return undefined; - } - - const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempToken); - return tempSession.sessionId; - } catch { - return undefined; - } -}; +/** + * 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 {NextRequest} from 'next/server'; +import SessionManager, {SessionTokenPayload} from './SessionManager'; + +/** + * Checks if a request has a valid session cookie (JWT). + * This verifies the JWT signature and expiration. + * + * @param request - The Next.js request object + * @returns True if a valid session exists, false otherwise + */ +export const hasValidSession = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return false; + } + + await SessionManager.verifySessionToken(sessionToken); + return true; + } catch { + return false; + } +}; + +/** + * Gets the session payload from the request cookies. + * This includes user ID, session ID, and scopes. + * + * @param request - The Next.js request object + * @returns The session payload if valid, undefined otherwise + */ +export const getSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionToken: string | undefined = request.cookies.get(SessionManager.getSessionCookieName(instanceId))?.value; + if (!sessionToken) { + return undefined; + } + + return await SessionManager.verifySessionToken(sessionToken); + } catch { + return undefined; + } +}; + +/** + * Gets the session ID from the request cookies (legacy support). + * First tries to get from JWT session, then falls back to legacy session ID cookie. + * + * @param request - The Next.js request object + * @returns The session ID if it exists, undefined otherwise + */ +export const getSessionIdFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const sessionPayload: SessionTokenPayload | undefined = await getSessionFromRequest(request, instanceId); + + if (sessionPayload) { + return sessionPayload.sessionId; + } + + return await Promise.resolve(undefined); + } catch { + return Promise.resolve(undefined); + } +}; + +/** + * Gets the temporary session ID from request cookies. + * + * @param request - The Next.js request object + * @returns The temporary session ID if valid, undefined otherwise + */ +export const getTempSessionFromRequest = async (request: NextRequest, instanceId: number = 0): Promise => { + try { + const tempToken: string | undefined = request.cookies.get(SessionManager.getTempSessionCookieName(instanceId))?.value; + if (!tempToken) { + return undefined; + } + + const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempToken); + return tempSession.sessionId; + } catch { + return undefined; + } +}; From 233eace62a715df668220990ee4f25e913492596 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Wed, 11 Mar 2026 18:26:02 +0530 Subject: [PATCH 7/7] Refactor Asgardeo client classes to include instanceId parameter for improved session handling --- packages/nextjs/src/AsgardeoNextClient.ts | 1 + .../src/client/contexts/Asgardeo/AsgardeoProvider.tsx | 6 +++++- packages/nextjs/src/server/AsgardeoProvider.tsx | 1 + packages/node/src/__legacy__/client.ts | 4 ++-- packages/node/src/__legacy__/core/authentication.ts | 4 ++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 2ef15026f..179540ee8 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -169,6 +169,7 @@ class AsgardeoNextClient exte ...rest, } as any, storage, + this.instanceId, ); } diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index 475192363..ea5026f7e 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -104,6 +104,7 @@ const AsgardeoClientProvider: FC> getAllOrganizations, switchOrganization, brandingPreference, + instanceId = 0, }: PropsWithChildren) => { const reRenderCheckRef: RefObject = useRef(false); const router: AppRouterInstance = useRouter(); @@ -151,8 +152,11 @@ const AsgardeoClientProvider: FC> return; } + // Check for what instance the callback is for + const callbackInstanceId: string | null = state ? state.split('_')[1] : null; + // Handle OAuth callback if code and state are present - if (code && state) { + if (code && state && callbackInstanceId === instanceId.toString()) { setIsLoading(true); const result: {error?: string; redirectUrl?: string; success: boolean} = await handleOAuthCallback( diff --git a/packages/nextjs/src/server/AsgardeoProvider.tsx b/packages/nextjs/src/server/AsgardeoProvider.tsx index d198b23ea..65bdbc3e2 100644 --- a/packages/nextjs/src/server/AsgardeoProvider.tsx +++ b/packages/nextjs/src/server/AsgardeoProvider.tsx @@ -228,6 +228,7 @@ const AsgardeoServerProvider: FC> switchOrganization={boundSwitchOrganization} brandingPreference={brandingPreference} createOrganization={boundCreateOrganization} + instanceId={instanceId} > {children} diff --git a/packages/node/src/__legacy__/client.ts b/packages/node/src/__legacy__/client.ts index 2fdfe7ad9..44bdef5b0 100644 --- a/packages/node/src/__legacy__/client.ts +++ b/packages/node/src/__legacy__/client.ts @@ -63,8 +63,8 @@ export class AsgardeoNodeClient { // eslint-disable-next-line @typescript-eslint/no-empty-function constructor() {} - public async initialize(config: AuthClientConfig, store?: Storage): Promise { - this.authCore = new AsgardeoNodeCore(config, store); + public async initialize(config: AuthClientConfig, store?: Storage, instanceId?: number): Promise { + this.authCore = new AsgardeoNodeCore(config, store, instanceId); return Promise.resolve(true); } diff --git a/packages/node/src/__legacy__/core/authentication.ts b/packages/node/src/__legacy__/core/authentication.ts index f5edfebab..0c0007b32 100644 --- a/packages/node/src/__legacy__/core/authentication.ts +++ b/packages/node/src/__legacy__/core/authentication.ts @@ -44,7 +44,7 @@ export class AsgardeoNodeCore { private storageManager: StorageManager; - constructor(config: AuthClientConfig, store?: Storage) { + constructor(config: AuthClientConfig, store?: Storage, instanceId?: number) { // Initialize the default memory cache store if an external store is not passed. if (!store) { this.store = new MemoryCacheStore(); @@ -53,7 +53,7 @@ export class AsgardeoNodeCore { } this.cryptoUtils = new NodeCryptoUtils(); this.auth = new AsgardeoAuthClient(); - this.auth.initialize(config, this.store, this.cryptoUtils); + this.auth.initialize(config, this.store, this.cryptoUtils, instanceId); this.storageManager = this.auth.getStorageManager(); Logger.debug('Initialized AsgardeoAuthClient successfully'); }