From 8c1af834d5ab84d36036c017fa907ca0ce089d3c Mon Sep 17 00:00:00 2001 From: Hasini Samarathunga Date: Mon, 18 May 2026 15:20:14 +0530 Subject: [PATCH 1/3] Add SDK methods for agent sub organization auth --- .../src/AsgardeoJavaScriptClient.ts | 328 +++++++++++++++++- packages/javascript/src/index.ts | 88 ++--- packages/javascript/src/models/agent.ts | 6 +- .../javascript/src/models/organization.ts | 13 +- packages/javascript/src/models/token.ts | 2 +- packages/nextjs/src/AsgardeoNextClient.ts | 2 +- tsconfig.json | 1 + 7 files changed, 389 insertions(+), 51 deletions(-) diff --git a/packages/javascript/src/AsgardeoJavaScriptClient.ts b/packages/javascript/src/AsgardeoJavaScriptClient.ts index 2998b15ab..651057394 100644 --- a/packages/javascript/src/AsgardeoJavaScriptClient.ts +++ b/packages/javascript/src/AsgardeoJavaScriptClient.ts @@ -39,12 +39,64 @@ import { EmbeddedSignInFlowStatus, } from './models/embedded-signin-flow'; import {OIDCDiscoveryApiResponse} from './models/oidc-discovery'; -import {AllOrganizationsApiResponse, Organization} from './models/organization'; +import {AllOrganizationsApiResponse, Organization, OrgDiscoveryType} from './models/organization'; import {Storage} from './models/store'; import {TokenExchangeRequestConfig, TokenResponse} from './models/token'; import {User, UserProfile} from './models/user'; import StorageManager from './StorageManager'; +/** + * Authorization parameter keys that are managed by the SDK when building an organization authorization URL. + */ +const RESERVED_AUTH_KEYS: ReadonlySet = new Set([ + 'client_id', + 'redirect_uri', + 'scope', + 'state', + 'response_type', + 'resource', + 'fidp', + 'requested_actor', + 'orgId', + 'orgHandle', + 'org', + 'login_hint', + 'orgDiscoveryType', + 'code_challenge', + 'code_challenge_method', +]); + +/** + * Options accepted by {@link AsgardeoJavaScriptClient.getOrgAuthorizationUrl}. + */ +export interface OrgAuthorizationUrlOptions { + /** + * Additional query parameters to append to the authorization URL. + */ + additionalParams?: Record; + /** + * Optional agent configuration. When provided, the resulting URL is + * decorated with `requested_actor=` so the issued user token is + * delegated on-behalf-of the agent. + */ + agentConfig?: AgentConfig; + /** + * When `true`, the `fidp=OrganizationSSO` query parameter is omitted so the + * enhanced organization authentication flow can take over discovery. + * Defaults to `false`. + */ + isEnhancedOrgAuth?: boolean; + /** + * Optional `resource` query parameter to forward to the authorize endpoint. + */ + resource?: string; + /** + * Optional `state` value to seed request correlation with. If omitted, the + * underlying SDK generates one. + */ + state?: string; +} + class AsgardeoJavaScriptClient implements AsgardeoClient { private cacheStore: Storage; @@ -56,20 +108,29 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { private baseURL: string; + private initPromise: Promise | undefined; + constructor(config?: AuthClientConfig, cacheStore?: Storage, cryptoUtils?: Crypto) { this.cacheStore = cacheStore ?? new DefaultCacheStore(); this.cryptoUtils = cryptoUtils ?? new DefaultCrypto(); this.auth = new AsgardeoAuthClient(); if (config) { - this.auth.initialize(config, this.cacheStore, this.cryptoUtils); + this.initPromise = this.auth.initialize(config, this.cacheStore, this.cryptoUtils); this.storageManager = this.auth.getStorageManager(); } this.baseURL = config?.baseUrl ?? ''; } + protected async ensureInitialized(): Promise { + if (this.initPromise) { + await this.initPromise; + } + } + public async getDiscoveryResponse(): Promise { + await this.ensureInitialized(); if (!this.storageManager) { return null; } @@ -178,6 +239,18 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { /* eslint-enable class-methods-use-this, @typescript-eslint/no-unused-vars */ public async getAgentToken(agentConfig: AgentConfig): Promise { + await this.ensureInitialized(); + + if (!agentConfig?.agentID) { + throw new Error('agentConfig.agentID is required for getAgentToken().'); + } + if (!agentConfig.agentSecret) { + throw new Error( + 'agentConfig.agentSecret is required for getAgentToken(). ' + + 'The agent must authenticate against the token endpoint.', + ); + } + const customParam: Record = { response_mode: 'direct', }; @@ -228,6 +301,7 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { } public async getOBOSignInURL(agentConfig: AgentConfig): Promise { + await this.ensureInitialized(); const customParam: Record = { requested_actor: agentConfig.agentID, }; @@ -258,6 +332,256 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { tokenRequestConfig, ); } + + /** + * Builds a `/oauth2/authorize` URL targeting a specific child organization. + * + * The target organization can be identified by its UUID (`orgID`), handle + * (`orgHandle`), display name (`org`) or via email-domain based discovery + * (`emailDomain`). + * + * @param orgDiscoveryType - The organization discovery strategy to use. + * @param discoveryValue - The identifier whose meaning depends on + * `orgDiscoveryType` (UUID, handle, name or email). + * @param options - Optional state, resource, agent delegation and + * additional query parameters. + * @returns The fully-built authorization URL. + */ + public async getOrgAuthorizationUrl( + orgDiscoveryType: OrgDiscoveryType, + discoveryValue: string, + options: OrgAuthorizationUrlOptions = {}, + ): Promise { + await this.ensureInitialized(); + const customParam: Record = AsgardeoJavaScriptClient.buildOrgAuthorizationParams( + orgDiscoveryType, + discoveryValue, + options, + ); + + const authURL: string | undefined = await this.auth.getSignInUrl(customParam); + + if (!authURL) { + throw new Error('Could not build organization authorization URL'); + } + + return authURL.toString(); + } + + /** + * Exchanges an existing access token for one scoped to a target organization, + * using the `organization_switch` grant type. + * + * Unlike {@link AsgardeoJavaScriptClient.exchangeToken} this method does + * not require an active SDK session — the caller supplies the source + * access token directly. This makes it safe to use from server-side agent + * flows where there is no user session yet. + * + * @param token - The current access token to be switched. + * @param switchingOrganization - The ID/UUID of the target organization. + * @param scopes - Optional list of scopes to request for the switched token. + * @returns A normalized {@link TokenResponse} for the switched organization. + */ + public async switchTokenToOrganization( + token: string, + switchingOrganization: string, + scopes?: string[], + ): Promise { + await this.ensureInitialized(); + if (!token) { + throw new Error('Token is required for organization switch.'); + } + if (!switchingOrganization) { + throw new Error('switchingOrganization is required.'); + } + + if (!this.storageManager) { + throw new Error('Client is not initialized. Call initialize() before switching organizations.'); + } + + const configData: AuthClientConfig | null = await this.storageManager.getConfigData(); + + if (!configData) { + throw new Error('Client configuration is unavailable. Initialize the client before switching organizations.'); + } + + const tokenEndpoint: string = await this.resolveTokenEndpoint(); + + const body: URLSearchParams = new URLSearchParams(); + const {clientId, clientSecret} = configData as unknown as {clientId: string; clientSecret?: string}; + const hasSecret: boolean = Boolean(clientSecret && clientSecret.trim().length > 0); + + body.set('grant_type', 'organization_switch'); + body.set('token', token); + body.set('switching_organization', switchingOrganization); + body.set('client_id', clientId); + + if (hasSecret) { + body.set('client_secret', clientSecret as string); + } + + if (scopes && scopes.length > 0) { + body.set('scope', scopes.join(' ')); + } + + let response: Response; + try { + response = await fetch(tokenEndpoint, { + body, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + }); + } catch (error) { + throw new Error(`Organization switch request failed: ${(error as Error)?.message ?? String(error)}`); + } + + if (!response.ok) { + let errorBody: string; + try { + errorBody = JSON.stringify(await response.json()); + } catch { + errorBody = response.statusText; + } + throw new Error(`Organization switch failed (${response.status}): ${errorBody}`); + } + + const parsed: { + access_token: string; + created_at?: number; + expires_in: string; + id_token: string; + refresh_token: string; + scope: string; + token_type: string; + } = await response.json(); + + return { + accessToken: parsed.access_token, + createdAt: parsed.created_at ?? Date.now(), + expiresIn: parsed.expires_in, + idToken: parsed.id_token, + refreshToken: parsed.refresh_token, + scope: parsed.scope, + tokenType: parsed.token_type, + }; + } + + /** + * Resolves the OAuth2 token endpoint URL. + * + * Prefers the value advertised by the OIDC well-known document (when it + * has already been loaded into the storage manager) and falls back to + * `${baseURL}/oauth2/token` derived from the SDK configuration. + */ + private async resolveTokenEndpoint(): Promise { + const discovery: OIDCDiscoveryApiResponse | null = this.storageManager + ? await this.storageManager.loadOpenIDProviderConfiguration() + : null; + + const discovered: string | undefined = discovery?.token_endpoint; + if (discovered && discovered.trim().length > 0) { + return discovered; + } + + if (this.baseURL && this.baseURL.trim().length > 0) { + return `${this.baseURL.replace(/\/$/, '')}/oauth2/token`; + } + + throw new Error( + 'Unable to resolve the token endpoint. Provide a baseUrl in the client configuration or ensure OIDC discovery has been performed.', + ); + } + + /** + * Authenticates as the agent and switches the issued agent token into a + * target child organization in a single call. + * + * @param agentConfig - Agent credentials used to obtain the parent-org agent token. + * @param switchingOrganization - The ID/UUID of the target organization. + * @param orgScopes - Optional scopes to request for the organization-scoped token. + * @returns A normalized {@link TokenResponse} scoped to the target organization. + */ + public async getOrganizationAgentToken( + agentConfig: AgentConfig, + switchingOrganization: string, + orgScopes?: string[], + ): Promise { + if (!switchingOrganization) { + throw new Error('switchingOrganization is required.'); + } + + const agentToken: TokenResponse = await this.getAgentToken(agentConfig); + + return this.switchTokenToOrganization(agentToken.accessToken, switchingOrganization, orgScopes); + } + + /** + * Builds the custom query-parameter map for an organization-scoped authorization request. + */ + private static buildOrgAuthorizationParams( + orgDiscoveryType: OrgDiscoveryType, + discoveryValue: string, + options: OrgAuthorizationUrlOptions, + ): Record { + const trimmedValue: string = (discoveryValue ?? '').trim(); + + if (!trimmedValue) { + throw new Error('discoveryValue is required.'); + } + + const customParam: Record = {}; + + if (!options.isEnhancedOrgAuth) { + customParam['fidp'] = 'OrganizationSSO'; + } + + switch (orgDiscoveryType) { + case 'orgID': + customParam['orgId'] = trimmedValue; + break; + case 'orgHandle': + customParam['orgHandle'] = trimmedValue; + break; + case 'org': + customParam['org'] = trimmedValue; + break; + case 'emailDomain': + customParam['login_hint'] = trimmedValue; + customParam['orgDiscoveryType'] = 'emailDomain'; + break; + default: + throw new Error(`Unsupported orgDiscoveryType: ${orgDiscoveryType as string}`); + } + + if (options.resource) { + customParam['resource'] = options.resource; + } + + if (options.state) { + customParam['state'] = options.state; + } + + if (options.agentConfig) { + customParam['requested_actor'] = options.agentConfig.agentID; + } + + if (options.additionalParams) { + const conflicts: string[] = Object.keys(options.additionalParams).filter((key: string) => + RESERVED_AUTH_KEYS.has(key), + ); + + if (conflicts.length > 0) { + throw new Error(`Reserved authorization parameters cannot be overridden: ${conflicts.sort().join(', ')}`); + } + + Object.assign(customParam, options.additionalParams); + } + + return customParam; + } } export default AsgardeoJavaScriptClient; diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index 8777f0546..b9c29af02 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -17,7 +17,7 @@ */ export {AsgardeoAuthClient} from './__legacy__/client'; -export { +export type { DefaultAuthClientConfig, WellKnownAuthClientConfig, BaseURLAuthClientConfig, @@ -32,23 +32,25 @@ export {default as initializeEmbeddedSignInFlow} from './api/initializeEmbeddedS export {default as executeEmbeddedSignInFlow} from './api/executeEmbeddedSignInFlow'; export {default as executeEmbeddedSignUpFlow} from './api/executeEmbeddedSignUpFlow'; export {default as getUserInfo} from './api/getUserInfo'; -export {default as getScim2Me, GetScim2MeConfig} from './api/getScim2Me'; -export {default as getSchemas, GetSchemasConfig} from './api/getSchemas'; -export {default as getAllOrganizations, GetAllOrganizationsConfig} from './api/getAllOrganizations'; -export { +export type {default as getScim2Me, GetScim2MeConfig} from './api/getScim2Me'; +export type {default as getSchemas, GetSchemasConfig} from './api/getSchemas'; +export type {default as getAllOrganizations, GetAllOrganizationsConfig} from './api/getAllOrganizations'; +export type { default as createOrganization, CreateOrganizationPayload, CreateOrganizationConfig, } from './api/createOrganization'; -export {default as getMeOrganizations, GetMeOrganizationsConfig} from './api/getMeOrganizations'; -export {default as getOrganization, OrganizationDetails, GetOrganizationConfig} from './api/getOrganization'; -export {default as updateOrganization, createPatchOperations, UpdateOrganizationConfig} from './api/updateOrganization'; -export {default as updateMeProfile, UpdateMeProfileConfig} from './api/updateMeProfile'; -export {default as getBrandingPreference, GetBrandingPreferenceConfig} from './api/getBrandingPreference'; +export type {default as getMeOrganizations, GetMeOrganizationsConfig} from './api/getMeOrganizations'; +export type {default as getOrganization, OrganizationDetails, GetOrganizationConfig} from './api/getOrganization'; +export {default as updateOrganization, createPatchOperations} from './api/updateOrganization'; +export type {UpdateOrganizationConfig} from './api/updateOrganization'; +export {default as updateMeProfile} from './api/updateMeProfile'; +export type {UpdateMeProfileConfig} from './api/updateMeProfile'; +export type {default as getBrandingPreference, GetBrandingPreferenceConfig} from './api/getBrandingPreference'; export {default as executeEmbeddedSignInFlowV2} from './api/v2/executeEmbeddedSignInFlowV2'; export {default as executeEmbeddedSignUpFlowV2} from './api/v2/executeEmbeddedSignUpFlowV2'; export {default as executeEmbeddedRecoveryFlowV2} from './api/v2/executeEmbeddedRecoveryFlowV2'; -export { +export type { default as executeEmbeddedUserOnboardingFlowV2, EmbeddedUserOnboardingFlowResponse, } from './api/v2/executeEmbeddedUserOnboardingFlowV2'; @@ -65,9 +67,9 @@ export {default as AsgardeoAPIError} from './errors/AsgardeoAPIError'; export {default as AsgardeoRuntimeError} from './errors/AsgardeoRuntimeError'; export {AsgardeoAuthException} from './errors/exception'; -export {AllOrganizationsApiResponse} from './models/organization'; +export type {AllOrganizationsApiResponse} from './models/organization'; export {Platform} from './models/platforms'; -export { +export type { EmbeddedSignInFlowInitiateResponse, EmbeddedSignInFlowStatus, EmbeddedSignInFlowType, @@ -80,7 +82,7 @@ export { EmbeddedSignInFlowAuthenticatorPromptType, EmbeddedSignInFlowAuthenticatorKnownIdPType, } from './models/embedded-signin-flow'; -export { +export type { EmbeddedFlowComponentType as EmbeddedFlowComponentTypeV2, EmbeddedFlowActionVariant as EmbeddedFlowActionVariantV2, EmbeddedFlowTextVariant as EmbeddedFlowTextVariantV2, @@ -94,7 +96,7 @@ export { ConsentPurposeData as ConsentPurposeDataV2, ConsentPromptData as ConsentPromptDataV2, } from './models/v2/embedded-flow-v2'; -export { +export type { EmbeddedSignInFlowStatus as EmbeddedSignInFlowStatusV2, EmbeddedSignInFlowType as EmbeddedSignInFlowTypeV2, ExtendedEmbeddedSignInFlowResponse as ExtendedEmbeddedSignInFlowResponseV2, @@ -103,7 +105,7 @@ export { EmbeddedSignInFlowInitiateRequest as EmbeddedSignInFlowInitiateRequestV2, EmbeddedSignInFlowRequest as EmbeddedSignInFlowRequestV2, } from './models/v2/embedded-signin-flow-v2'; -export { +export type { EmbeddedSignUpFlowStatus as EmbeddedSignUpFlowStatusV2, EmbeddedSignUpFlowType as EmbeddedSignUpFlowTypeV2, ExtendedEmbeddedSignUpFlowResponse as ExtendedEmbeddedSignUpFlowResponseV2, @@ -113,7 +115,7 @@ export { EmbeddedSignUpFlowRequest as EmbeddedSignUpFlowRequestV2, EmbeddedSignUpFlowErrorResponse as EmbeddedSignUpFlowErrorResponseV2, } from './models/v2/embedded-signup-flow-v2'; -export { +export type { EmbeddedRecoveryFlowStatus as EmbeddedRecoveryFlowStatusV2, EmbeddedRecoveryFlowType as EmbeddedRecoveryFlowTypeV2, EmbeddedRecoveryFlowResponse as EmbeddedRecoveryFlowResponseV2, @@ -121,12 +123,12 @@ export { EmbeddedRecoveryFlowRequest as EmbeddedRecoveryFlowRequestV2, EmbeddedRecoveryFlowErrorResponse as EmbeddedRecoveryFlowErrorResponseV2, } from './models/v2/embedded-recovery-flow-v2'; -export { +export type { OrganizationUnit, OrganizationUnitListResponse, GetOrganizationUnitChildrenConfig, } from './models/v2/organization-unit'; -export { +export type { FlowMetaType, ApplicationMetadata, OUMetadata, @@ -143,7 +145,7 @@ export { FlowMetaThemeShape, FlowMetaThemeTypography, } from './models/v2/flow-meta-v2'; -export { +export type { EmbeddedFlowType, EmbeddedFlowStatus, EmbeddedFlowExecuteResponse, @@ -156,8 +158,8 @@ export { EmbeddedFlowExecuteErrorResponse, } from './models/embedded-flow'; export {FlowMode} from './models/flow'; -export {AsgardeoClient} from './models/client'; -export { +export type {AsgardeoClient} from './models/client'; +export type { BaseConfig, Config, Preferences, @@ -171,27 +173,27 @@ export { SignOutOptions, SignUpOptions, } from './models/config'; -export {TokenEndpointAuthMethod} from './models/token-endpoint-auth'; +export type {TokenEndpointAuthMethod} from './models/token-endpoint-auth'; export type {ComponentRenderContext, ComponentRenderer, ComponentsExtensions} from './models/v2/extensions/components'; -export {TokenResponse, IdToken, TokenExchangeRequestConfig} from './models/token'; -export {AgentConfig} from './models/agent'; -export {AuthCodeResponse} from './models/auth-code-response'; -export {Crypto, JWKInterface} from './models/crypto'; -export {OAuthResponseMode} from './models/oauth-response'; -export { +export type {TokenResponse, IdToken, TokenExchangeRequestConfig} from './models/token'; +export type {AgentConfig} from './models/agent'; +export type {AuthCodeResponse} from './models/auth-code-response'; +export type {Crypto, JWKInterface} from './models/crypto'; +export type {OAuthResponseMode} from './models/oauth-response'; +export type { AuthorizeRequestUrlParams, KnownExtendedAuthorizeRequestUrlParams, ExtendedAuthorizeRequestUrlParams, } from './models/oauth-request'; -export {OIDCEndpoints} from './models/oidc-endpoints'; -export {OIDCDiscoveryApiResponse} from './models/oidc-discovery'; -export {Storage, TemporaryStore} from './models/store'; -export {User, UserProfile} from './models/user'; -export {SessionData} from './models/session'; -export {Organization} from './models/organization'; -export {TranslationFn} from './models/v2/translation'; -export {ResolveFlowTemplateLiteralsOptions} from './models/v2/vars'; -export { +export type {OIDCEndpoints} from './models/oidc-endpoints'; +export type {OIDCDiscoveryApiResponse} from './models/oidc-discovery'; +export type {Storage, TemporaryStore} from './models/store'; +export type {User, UserProfile} from './models/user'; +export type {SessionData} from './models/session'; +export type {Organization} from './models/organization'; +export type {TranslationFn} from './models/v2/translation'; +export type {ResolveFlowTemplateLiteralsOptions} from './models/v2/vars'; +export type { BrandingPreference, BrandingPreferenceConfig, BrandingLayout, @@ -203,14 +205,14 @@ export { BrandingOrganizationDetails, UrlsConfig, } from './models/branding-preference'; -export {Schema, SchemaAttribute, WellKnownSchemaIds, FlattenedSchema} from './models/scim2-schema'; -export {RecursivePartial} from './models/utility-types'; +export type {Schema, SchemaAttribute, WellKnownSchemaIds, FlattenedSchema} from './models/scim2-schema'; +export type {RecursivePartial} from './models/utility-types'; export {FieldType} from './models/field'; export {default as AsgardeoJavaScriptClient} from './AsgardeoJavaScriptClient'; export {default as createTheme, DEFAULT_THEME} from './theme/createTheme'; -export {ThemeColors, ThemeConfig, Theme, ThemeMode, ThemeDetection} from './theme/types'; +export type {ThemeColors, ThemeConfig, Theme, ThemeMode, ThemeDetection} from './theme/types'; export {default as arrayBufferToBase64url} from './utils/arrayBufferToBase64url'; export {default as base64urlToArrayBuffer} from './utils/base64urlToArrayBuffer'; @@ -245,7 +247,7 @@ export {default as processOpenIDScopes} from './utils/processOpenIDScopes'; export {default as withVendorCSSClassPrefix} from './utils/withVendorCSSClassPrefix'; export {default as transformBrandingPreferenceToTheme} from './utils/transformBrandingPreferenceToTheme'; -export { +export type { default as logger, createLogger, createComponentLogger, @@ -263,4 +265,4 @@ export type {LoggerConfig} from './utils/logger'; export {default as StorageManager} from './StorageManager'; export {HttpClient} from './HttpClient'; -export {HttpError, HttpRequestConfig, HttpResponse} from './models/http'; +export type {HttpError, HttpRequestConfig, HttpResponse} from './models/http'; diff --git a/packages/javascript/src/models/agent.ts b/packages/javascript/src/models/agent.ts index abeb657ff..2402a1a7f 100644 --- a/packages/javascript/src/models/agent.ts +++ b/packages/javascript/src/models/agent.ts @@ -21,13 +21,13 @@ */ export interface AgentConfig { /** - * The unique identifier for the agent + * The unique identifier for the agent. */ agentID: string; /** - * The secret credential for the agent + * The secret credential for the agent. */ - agentSecret: string; + agentSecret?: string; /** * The authenticator name to match during the embedded sign-in flow. * Defaults to {@link AgentConfig.DEFAULT_AUTHENTICATOR_NAME} if not provided. diff --git a/packages/javascript/src/models/organization.ts b/packages/javascript/src/models/organization.ts index fd166ca3d..b6af2ae96 100644 --- a/packages/javascript/src/models/organization.ts +++ b/packages/javascript/src/models/organization.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2025-2026, 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 @@ -33,3 +33,14 @@ export interface AllOrganizationsApiResponse { organizations: Organization[]; totalCount?: number; } + +/** + * Supported organization discovery types used when building an + * organization-scoped authorization URL. + * + * - `orgID` : organization UUID + * - `orgHandle` : organization handle + * - `org` : organization name + * - `emailDomain` : user email address + */ +export type OrgDiscoveryType = 'orgID' | 'orgHandle' | 'org' | 'emailDomain'; diff --git a/packages/javascript/src/models/token.ts b/packages/javascript/src/models/token.ts index a77038f35..2f6a0dddb 100644 --- a/packages/javascript/src/models/token.ts +++ b/packages/javascript/src/models/token.ts @@ -33,7 +33,7 @@ export interface TokenResponse { accessToken: string; /** - * Unix timestamp (in seconds) when the token was created. + * Unix timestamp (in milliseconds) when the token was created. * Used in combination with expiresIn to determine when * the token needs to be refreshed. */ diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 3662d6c50..7002a8cd5 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -97,7 +97,7 @@ class AsgardeoNextClient exte * Ensures the client is initialized before using it. * Throws an error if the client is not initialized. */ - private async ensureInitialized(): Promise { + protected override async ensureInitialized(): Promise { if (!this.isInitialized) { throw new Error( '[AsgardeoNextClient] Client is not initialized. Make sure you have wrapped your app with AsgardeoProvider and provided the required configuration (baseUrl, clientId, etc.).', diff --git a/tsconfig.json b/tsconfig.json index dc121f199..d3404b478 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "baseUrl": ".", "paths": { + "@asgardeo/javascript": ["packages/javascript/src/index.ts"], "@asgardeo/react": ["packages/react/src/index.ts"], } } From 86dfb39eabd9c72430a9e58d2252b7f21e42bbba Mon Sep 17 00:00:00 2001 From: Hasini Samarathunga Date: Tue, 19 May 2026 08:28:38 +0530 Subject: [PATCH 2/3] Add review suggestions on code quality --- .../src/AsgardeoJavaScriptClient.ts | 18 +++-- packages/javascript/src/index.ts | 81 +++++++++++-------- prettier.config.cjs | 2 +- 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/packages/javascript/src/AsgardeoJavaScriptClient.ts b/packages/javascript/src/AsgardeoJavaScriptClient.ts index 651057394..660b4ebf9 100644 --- a/packages/javascript/src/AsgardeoJavaScriptClient.ts +++ b/packages/javascript/src/AsgardeoJavaScriptClient.ts @@ -341,7 +341,7 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { * (`emailDomain`). * * @param orgDiscoveryType - The organization discovery strategy to use. - * @param discoveryValue - The identifier whose meaning depends on + * @param discoveryInput - The identifier whose meaning depends on * `orgDiscoveryType` (UUID, handle, name or email). * @param options - Optional state, resource, agent delegation and * additional query parameters. @@ -349,13 +349,13 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { */ public async getOrgAuthorizationUrl( orgDiscoveryType: OrgDiscoveryType, - discoveryValue: string, + discoveryInput: string, options: OrgAuthorizationUrlOptions = {}, ): Promise { await this.ensureInitialized(); const customParam: Record = AsgardeoJavaScriptClient.buildOrgAuthorizationParams( orgDiscoveryType, - discoveryValue, + discoveryInput, options, ); @@ -409,6 +409,9 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { const body: URLSearchParams = new URLSearchParams(); const {clientId, clientSecret} = configData as unknown as {clientId: string; clientSecret?: string}; + if (!clientId || clientId.trim().length === 0) { + throw new Error('clientId is required in the client configuration for organization switch.'); + } const hasSecret: boolean = Boolean(clientSecret && clientSecret.trim().length > 0); body.set('grant_type', 'organization_switch'); @@ -523,13 +526,13 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { */ private static buildOrgAuthorizationParams( orgDiscoveryType: OrgDiscoveryType, - discoveryValue: string, + discoveryInput: string, options: OrgAuthorizationUrlOptions, ): Record { - const trimmedValue: string = (discoveryValue ?? '').trim(); + const trimmedValue: string = (discoveryInput ?? '').trim(); if (!trimmedValue) { - throw new Error('discoveryValue is required.'); + throw new Error('discoveryInput is required.'); } const customParam: Record = {}; @@ -565,6 +568,9 @@ class AsgardeoJavaScriptClient implements AsgardeoClient { } if (options.agentConfig) { + if (!options.agentConfig.agentID || options.agentConfig.agentID.trim().length === 0) { + throw new Error('agentConfig.agentID is required when agentConfig is provided.'); + } customParam['requested_actor'] = options.agentConfig.agentID; } diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index b9c29af02..ac913d7e7 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -32,28 +32,29 @@ export {default as initializeEmbeddedSignInFlow} from './api/initializeEmbeddedS export {default as executeEmbeddedSignInFlow} from './api/executeEmbeddedSignInFlow'; export {default as executeEmbeddedSignUpFlow} from './api/executeEmbeddedSignUpFlow'; export {default as getUserInfo} from './api/getUserInfo'; -export type {default as getScim2Me, GetScim2MeConfig} from './api/getScim2Me'; -export type {default as getSchemas, GetSchemasConfig} from './api/getSchemas'; -export type {default as getAllOrganizations, GetAllOrganizationsConfig} from './api/getAllOrganizations'; -export type { - default as createOrganization, - CreateOrganizationPayload, - CreateOrganizationConfig, -} from './api/createOrganization'; -export type {default as getMeOrganizations, GetMeOrganizationsConfig} from './api/getMeOrganizations'; -export type {default as getOrganization, OrganizationDetails, GetOrganizationConfig} from './api/getOrganization'; +export {default as getScim2Me} from './api/getScim2Me'; +export type {GetScim2MeConfig} from './api/getScim2Me'; +export {default as getSchemas} from './api/getSchemas'; +export type {GetSchemasConfig} from './api/getSchemas'; +export {default as getAllOrganizations} from './api/getAllOrganizations'; +export type {GetAllOrganizationsConfig} from './api/getAllOrganizations'; +export {default as createOrganization} from './api/createOrganization'; +export type {CreateOrganizationPayload, CreateOrganizationConfig} from './api/createOrganization'; +export {default as getMeOrganizations} from './api/getMeOrganizations'; +export type {GetMeOrganizationsConfig} from './api/getMeOrganizations'; +export {default as getOrganization} from './api/getOrganization'; +export type {OrganizationDetails, GetOrganizationConfig} from './api/getOrganization'; export {default as updateOrganization, createPatchOperations} from './api/updateOrganization'; export type {UpdateOrganizationConfig} from './api/updateOrganization'; export {default as updateMeProfile} from './api/updateMeProfile'; export type {UpdateMeProfileConfig} from './api/updateMeProfile'; -export type {default as getBrandingPreference, GetBrandingPreferenceConfig} from './api/getBrandingPreference'; +export {default as getBrandingPreference} from './api/getBrandingPreference'; +export type {GetBrandingPreferenceConfig} from './api/getBrandingPreference'; export {default as executeEmbeddedSignInFlowV2} from './api/v2/executeEmbeddedSignInFlowV2'; export {default as executeEmbeddedSignUpFlowV2} from './api/v2/executeEmbeddedSignUpFlowV2'; export {default as executeEmbeddedRecoveryFlowV2} from './api/v2/executeEmbeddedRecoveryFlowV2'; -export type { - default as executeEmbeddedUserOnboardingFlowV2, - EmbeddedUserOnboardingFlowResponse, -} from './api/v2/executeEmbeddedUserOnboardingFlowV2'; +export {default as executeEmbeddedUserOnboardingFlowV2} from './api/v2/executeEmbeddedUserOnboardingFlowV2'; +export type {EmbeddedUserOnboardingFlowResponse} from './api/v2/executeEmbeddedUserOnboardingFlowV2'; export {default as getFlowMetaV2} from './api/v2/getFlowMetaV2'; export {default as getOrganizationUnitChildren} from './api/v2/getOrganizationUnitChildren'; @@ -69,24 +70,30 @@ export {AsgardeoAuthException} from './errors/exception'; export type {AllOrganizationsApiResponse} from './models/organization'; export {Platform} from './models/platforms'; -export type { - EmbeddedSignInFlowInitiateResponse, +export { EmbeddedSignInFlowStatus, EmbeddedSignInFlowType, EmbeddedSignInFlowStepType, - EmbeddedSignInFlowAuthenticator, - EmbeddedSignInFlowLink, - EmbeddedSignInFlowHandleRequestPayload, - EmbeddedSignInFlowHandleResponse, EmbeddedSignInFlowAuthenticatorParamType, EmbeddedSignInFlowAuthenticatorPromptType, EmbeddedSignInFlowAuthenticatorKnownIdPType, } from './models/embedded-signin-flow'; export type { + EmbeddedSignInFlowInitiateResponse, + EmbeddedSignInFlowAuthenticator, + EmbeddedSignInFlowLink, + EmbeddedSignInFlowHandleRequestPayload, + EmbeddedSignInFlowHandleResponse, +} from './models/embedded-signin-flow'; +export { EmbeddedFlowComponentType as EmbeddedFlowComponentTypeV2, + EmbeddedFlowEventType as EmbeddedFlowEventTypeV2, +} from './models/v2/embedded-flow-v2'; +export { EmbeddedFlowActionVariant as EmbeddedFlowActionVariantV2, EmbeddedFlowTextVariant as EmbeddedFlowTextVariantV2, - EmbeddedFlowEventType as EmbeddedFlowEventTypeV2, +} from './models/v2/embedded-flow-v2'; +export type { EmbeddedFlowComponent as EmbeddedFlowComponentV2, EmbeddedFlowResponseData as EmbeddedFlowResponseDataV2, EmbeddedFlowExecuteRequestConfig as EmbeddedFlowExecuteRequestConfigV2, @@ -96,18 +103,22 @@ export type { ConsentPurposeData as ConsentPurposeDataV2, ConsentPromptData as ConsentPromptDataV2, } from './models/v2/embedded-flow-v2'; -export type { +export { EmbeddedSignInFlowStatus as EmbeddedSignInFlowStatusV2, EmbeddedSignInFlowType as EmbeddedSignInFlowTypeV2, +} from './models/v2/embedded-signin-flow-v2'; +export type { ExtendedEmbeddedSignInFlowResponse as ExtendedEmbeddedSignInFlowResponseV2, EmbeddedSignInFlowResponse as EmbeddedSignInFlowResponseV2, EmbeddedSignInFlowCompleteResponse as EmbeddedSignInFlowCompleteResponseV2, EmbeddedSignInFlowInitiateRequest as EmbeddedSignInFlowInitiateRequestV2, EmbeddedSignInFlowRequest as EmbeddedSignInFlowRequestV2, } from './models/v2/embedded-signin-flow-v2'; -export type { +export { EmbeddedSignUpFlowStatus as EmbeddedSignUpFlowStatusV2, EmbeddedSignUpFlowType as EmbeddedSignUpFlowTypeV2, +} from './models/v2/embedded-signup-flow-v2'; +export type { ExtendedEmbeddedSignUpFlowResponse as ExtendedEmbeddedSignUpFlowResponseV2, EmbeddedSignUpFlowResponse as EmbeddedSignUpFlowResponseV2, EmbeddedSignUpFlowCompleteResponse as EmbeddedSignUpFlowCompleteResponseV2, @@ -115,9 +126,11 @@ export type { EmbeddedSignUpFlowRequest as EmbeddedSignUpFlowRequestV2, EmbeddedSignUpFlowErrorResponse as EmbeddedSignUpFlowErrorResponseV2, } from './models/v2/embedded-signup-flow-v2'; -export type { +export { EmbeddedRecoveryFlowStatus as EmbeddedRecoveryFlowStatusV2, EmbeddedRecoveryFlowType as EmbeddedRecoveryFlowTypeV2, +} from './models/v2/embedded-recovery-flow-v2'; +export type { EmbeddedRecoveryFlowResponse as EmbeddedRecoveryFlowResponseV2, EmbeddedRecoveryFlowInitiateRequest as EmbeddedRecoveryFlowInitiateRequestV2, EmbeddedRecoveryFlowRequest as EmbeddedRecoveryFlowRequestV2, @@ -128,8 +141,8 @@ export type { OrganizationUnitListResponse, GetOrganizationUnitChildrenConfig, } from './models/v2/organization-unit'; +export {FlowMetaType} from './models/v2/flow-meta-v2'; export type { - FlowMetaType, ApplicationMetadata, OUMetadata, DesignMetadata, @@ -145,14 +158,16 @@ export type { FlowMetaThemeShape, FlowMetaThemeTypography, } from './models/v2/flow-meta-v2'; -export type { +export { + EmbeddedFlowComponentType, EmbeddedFlowType, EmbeddedFlowStatus, - EmbeddedFlowExecuteResponse, EmbeddedFlowResponseType, +} from './models/embedded-flow'; +export type { + EmbeddedFlowExecuteResponse, EmbeddedSignUpFlowData, EmbeddedFlowComponent, - EmbeddedFlowComponentType, EmbeddedFlowExecuteRequestPayload, EmbeddedFlowExecuteRequestConfig, EmbeddedFlowExecuteErrorResponse, @@ -205,7 +220,8 @@ export type { BrandingOrganizationDetails, UrlsConfig, } from './models/branding-preference'; -export type {Schema, SchemaAttribute, WellKnownSchemaIds, FlattenedSchema} from './models/scim2-schema'; +export {WellKnownSchemaIds} from './models/scim2-schema'; +export type {Schema, SchemaAttribute, FlattenedSchema} from './models/scim2-schema'; export type {RecursivePartial} from './models/utility-types'; export {FieldType} from './models/field'; @@ -247,20 +263,19 @@ export {default as processOpenIDScopes} from './utils/processOpenIDScopes'; export {default as withVendorCSSClassPrefix} from './utils/withVendorCSSClassPrefix'; export {default as transformBrandingPreferenceToTheme} from './utils/transformBrandingPreferenceToTheme'; -export type { +export { default as logger, createLogger, createComponentLogger, createPackageLogger, createPackageComponentLogger, - LogLevel, configure as configureLogger, debug, info, warn, error, } from './utils/logger'; -export type {LoggerConfig} from './utils/logger'; +export type {LogLevel, LoggerConfig} from './utils/logger'; export {default as StorageManager} from './StorageManager'; diff --git a/prettier.config.cjs b/prettier.config.cjs index 929b9b15f..242eb9a8d 100644 --- a/prettier.config.cjs +++ b/prettier.config.cjs @@ -16,4 +16,4 @@ * under the License. */ -module.exports = require('@wso2/prettier-config'); +module.exports = require('@wso2/prettier-config/packages/prettier-config'); From d2a768e69fedbffd21bf37905fe3e358492fc1be Mon Sep 17 00:00:00 2001 From: Hasini Samarathunga Date: Tue, 19 May 2026 08:45:14 +0530 Subject: [PATCH 3/3] =?UTF-8?q?Add=20changeset=20=F0=9F=A6=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/vast-bears-dance.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/vast-bears-dance.md diff --git a/.changeset/vast-bears-dance.md b/.changeset/vast-bears-dance.md new file mode 100644 index 000000000..91841909a --- /dev/null +++ b/.changeset/vast-bears-dance.md @@ -0,0 +1,6 @@ +--- +'@asgardeo/javascript': minor +'@asgardeo/nextjs': patch +--- + +Add SDK methods for agent sub organization auth