From a51bd669c2eda369f4124ad89996aee4478d48ee Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Mon, 27 Jan 2025 21:58:01 +0530 Subject: [PATCH 1/3] custom token exchange auth changes --- src/auth/index.ts | 7 +- src/auth/oauth.ts | 2 +- src/auth/tokenExchange.ts | 251 ++++++++++++++++++++++++++++++++ test/auth/tokenExchange.test.ts | 187 ++++++++++++++++++++++++ 4 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 src/auth/tokenExchange.ts create mode 100644 test/auth/tokenExchange.test.ts diff --git a/src/auth/index.ts b/src/auth/index.ts index 023849cef..3abc5a06a 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -1,8 +1,9 @@ -import { Backchannel } from './backchannel.js'; +import { Backchannel, IBackchannel } from './backchannel.js'; import { AuthenticationClientOptions } from './base-auth-api.js'; import { Database } from './database.js'; import { OAuth } from './oauth.js'; import { Passwordless } from './passwordless.js'; +import { CustomTokenExchange, ICustomTokenExchange } from './tokenExchange.js'; export * from './database.js'; export * from './oauth.js'; @@ -14,12 +15,14 @@ export class AuthenticationClient { database: Database; oauth: OAuth; passwordless: Passwordless; - backchannel: Backchannel; + backchannel: IBackchannel; + tokenExchange: ICustomTokenExchange; constructor(options: AuthenticationClientOptions) { this.database = new Database(options); this.oauth = new OAuth(options); this.passwordless = new Passwordless(options); this.backchannel = new Backchannel(options); + this.tokenExchange = new CustomTokenExchange(options); } } diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 2902b1051..8c4b93afd 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -270,7 +270,7 @@ export interface TokenExchangeGrantRequest { * OAuth 2.0 flows. */ export class OAuth extends BaseAuthAPI { - private idTokenValidator: IDTokenValidator; + readonly idTokenValidator: IDTokenValidator; constructor(options: AuthenticationClientOptions) { super({ ...options, diff --git a/src/auth/tokenExchange.ts b/src/auth/tokenExchange.ts new file mode 100644 index 000000000..425b3c7f7 --- /dev/null +++ b/src/auth/tokenExchange.ts @@ -0,0 +1,251 @@ +import { JSONApiResponse } from '../lib/models.js'; +import { BaseAuthAPI } from './base-auth-api.js'; + +/** + * Represents the configuration options required for initiating a Custom Token Exchange request + * following RFC 8693 specifications. + * + * @see {@link https://www.rfc-editor.org/rfc/rfc8693 | RFC 8693: OAuth 2.0 Token Exchange} + */ +export type CustomTokenExchangeOptions = { + /** + * The type identifier for the subject token being exchanged + * + * @pattern + * - Must be a namespaced URI under your organization's control + * - Forbidden patterns: + * - `^urn:ietf:params:oauth:*` (IETF reserved) + * - `^https:\/\/auth0\.com/*` (Auth0 reserved) + * - `^urn:auth0:*` (Auth0 reserved) + * + * @example + * "urn:acme:legacy-system-token" + * "https://api.yourcompany.com/token-type/v1" + */ + subject_token_type: string; + + /** + * The opaque token value being exchanged for Auth0 tokens + * + * @security + * - Must be validated in Auth0 Actions using strong cryptographic verification + * - Implement replay attack protection + * - Recommended validation libraries: `jose`, `jsonwebtoken` + * + * @example + * "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + */ + subject_token: string; + + /** + * The target audience for the requested Auth0 token + * + * @remarks + * Must match exactly with an API identifier configured in your Auth0 tenant + * + * @example + * "https://api.your-service.com/v1" + */ + audience: string; + + /** + * Space-separated list of OAuth 2.0 scopes being requested + * + * @remarks + * Subject to API authorization policies configured in Auth0 + * + * @example + * "openid profile email read:data write:data" + */ + scope?: string; + + /** + * Additional custom parameters for Auth0 Action processing + * + * @remarks + * Accessible in Action code via `event.request.body` + * + * @example + * ```typescript + * { + * custom_parameter: "session_context", + * device_fingerprint: "a3d8f7...", + * } + * ``` + */ + [key: string]: unknown; +}; + +/** + * Internal request body structure for token exchange endpoint + * + * @privateRemarks + * Combines user parameters with OAuth 2.0 required values and + * client authentication managed by BaseAuthAPI + */ +type CustomTokenExchangeRequestBody = CustomTokenExchangeOptions & { + /** @default "urn:ietf:params:oauth:grant-type:token-exchange" */ + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange'; + + /** Injected from BaseAuthAPI configuration */ + client_id: string; +}; + +/** + * Interface defining Custom Token Exchange operations + * + * @see {@link https://auth0.com/docs/authenticate/protocols/custom-token-exchange | Auth0 Custom Token Exchange Docs} + */ +export interface ICustomTokenExchange { + /** + * Executes RFC 8693-compliant token exchange flow + * + * @throws {Auth0Error} For structured error responses + * @throws {Error} For generic errors with these codes: + * - `invalid_request`: Invalid parameters + * - `consent_required`: Enable "Allow Skipping User Consent" in API settings + * - `too_many_attempts`: Suspicious IP throttling triggered + * + * @example + * ```typescript + * // External IdP migration scenario + * const tokens = await auth0.customTokenExchange.exchangeToken({ + * subject_token_type: 'urn:external-idp:legacy', + * subject_token: externalIdPToken, + * audience: 'https://api.your-service.com', + * scope: 'openid profile' + * }); + * ``` + */ + exchangeToken(options: CustomTokenExchangeOptions): Promise; +} + +/** RFC 8693-defined grant type for token exchange */ +const TOKEN_EXCHANGE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange'; +/** Auth0 token endpoint path */ +const TOKEN_URL = '/oauth/token'; + +/** + * Implements Auth0's Custom Token Exchange functionality with security best practices + * + * @security + * - **HTTPS Enforcement**: All requests require TLS encryption + * - **Credential Protection**: Client secrets never exposed in browser contexts + * - **Input Validation**: Strict namespace enforcement for token types + * + * @example + * ```typescript + * // Secure token validation in Auth0 Action + * exports.onExecuteCustomTokenExchange = async (event, api) => { + * const { jws } = require('jose'); + * const { createRemoteJWKSet } = require('jose/jwks'); + * + * const JWKS = createRemoteJWKSet(new URL('https://external-idp.com/.well-known/jwks.json')); + * + * try { + * const { payload } = await jws.verify(event.transaction.subject_token, JWKS); + * api.authentication.setUserById(payload.sub); + * } catch (error) { + * api.access.rejectInvalidSubjectToken('Invalid token signature'); + * } + * }; + * ``` + */ +export class CustomTokenExchange extends BaseAuthAPI implements ICustomTokenExchange { + /** + * Executes token exchange flow with security validations + * + * @param options - Exchange configuration parameters + * @returns Auth0-issued tokens with requested claims + * + * @throws {Error} When: + * - `subject_token_type` uses prohibited namespace + * - Network failures occur + * - Auth0 returns error responses (4xx/5xx) + */ + async exchangeToken(options: CustomTokenExchangeOptions): Promise { + this.validateTokenType(options.subject_token_type); + + const body: CustomTokenExchangeRequestBody = { + ...options, + grant_type: TOKEN_EXCHANGE_GRANT_TYPE, + client_id: this.clientId, + }; + + await this.addClientAuthentication(body); + + const response = await this.request( + { + path: TOKEN_URL, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(body as Record), + }, + {} + ); + + const r: JSONApiResponse = await JSONApiResponse.fromResponse(response); + return r.data; + } + + /** + * Enforces namespace ownership requirements for token types + * + * @param tokenType - Proposed subject_token_type value + * @throws {Error} When reserved namespace pattern detected + * + * @privateRemarks + * Implements RFC 8693 Section 4.1 requirements for token type URIs + * + * @see {@link https://www.rfc-editor.org/rfc/rfc8693#section-4.1 | RFC 8693 Section 4.1} + */ + private validateTokenType(tokenType: string): void { + const reservedPatterns = [ + /^urn:ietf:params:oauth:/i, + /^https:\/\/auth0\.com\//i, + /^urn:auth0:/i, + ]; + + if (reservedPatterns.some((pattern) => pattern.test(tokenType))) { + throw new Error( + `Invalid subject_token_type '${tokenType}'. ` + + `Reserved namespaces are prohibited. Use URIs under your organization's control.` + ); + } + } +} + +/** + * Standardized token response structure for Auth0 authentication flows + * + * @remarks + * **Token Lifetime Management**: + * - Cache tokens according to `expires_in` value + * - Rotate refresh tokens using `offline_access` scope + * - Revoke compromised tokens immediately + * + * @security + * - Store tokens in secure, encrypted storage + * - Never expose in client-side code or logs + */ +export type TokenResponse = { + /** Bearer token for API authorization */ + access_token: string; + + /** Refresh token (requires `offline_access` scope) */ + refresh_token?: string; + + /** JWT containing user identity claims */ + id_token: string; + + /** Typically "Bearer" */ + token_type?: string; + + /** Token validity in seconds (default: 86400) */ + expires_in: number; + + /** Granted permissions space */ + scope: string; +}; diff --git a/test/auth/tokenExchange.test.ts b/test/auth/tokenExchange.test.ts new file mode 100644 index 000000000..c9a38c14c --- /dev/null +++ b/test/auth/tokenExchange.test.ts @@ -0,0 +1,187 @@ +// custom-token-exchange.test.ts +import nock from 'nock'; +import { CustomTokenExchange, CustomTokenExchangeOptions } from '../../src/auth/tokenExchange.js'; +import { AuthenticationClientOptions } from '../../src/auth/base-auth-api.js'; + +const DOMAIN = 'test-tenant.auth0.com'; +const CLIENT_ID = 'TEST_CLIENT_ID'; +const CLIENT_SECRET = 'TEST_CLIENT_SECRET'; +const AUDIENCE = 'https://api.example.com'; + +const mockOptions: AuthenticationClientOptions = { + domain: DOMAIN, + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, +}; + +describe('CustomTokenExchange', () => { + let client: CustomTokenExchange; + + beforeAll(() => { + nock.disableNetConnect(); + }); + + afterAll(() => { + nock.enableNetConnect(); + }); + + beforeEach(() => { + client = new CustomTokenExchange(mockOptions); + nock.cleanAll(); + }); + + describe('exchangeToken()', () => { + const baseParams: CustomTokenExchangeOptions = { + subject_token_type: 'urn:test:token', + subject_token: 'external-token-123', + audience: AUDIENCE, + }; + + test('should successfully exchange valid token', async () => { + // Mock successful token response + nock(`https://${DOMAIN}`) + .post('/oauth/token', (body) => { + return ( + body.grant_type === 'urn:ietf:params:oauth:grant-type:token-exchange' && + body.subject_token_type === 'urn:test:token' && + body.client_id === CLIENT_ID + ); + }) + .reply(200, { + access_token: 'eyJ.ACCESS.TOKEN', + refresh_token: 'eyJ.REFRESH.TOKEN', + id_token: 'eyJ.ID.TOKEN', + token_type: 'Bearer', + expires_in: 86400, + scope: 'openid profile', + }); + + const result = await client.exchangeToken(baseParams); + + expect(result).toEqual({ + access_token: 'eyJ.ACCESS.TOKEN', + refresh_token: 'eyJ.REFRESH.TOKEN', + id_token: 'eyJ.ID.TOKEN', + token_type: 'Bearer', + expires_in: 86400, + scope: 'openid profile', + }); + }); + + test('should include optional scope parameter in request body', async () => { + nock(`https://${DOMAIN}`) + .post('/oauth/token', (body) => { + // Verify body contains URL-encoded scope parameter + return ( + body.scope === 'openid profile email' && + body.grant_type === 'urn:ietf:params:oauth:grant-type:token-exchange' + ); + }) + .reply(200, { + access_token: '...', + id_token: '...', + expires_in: 3600, + scope: 'openid profile email', + }); + + const result = await client.exchangeToken({ + ...baseParams, + scope: 'openid profile email', + }); + + expect(result.scope).toBe('openid profile email'); + }); + + test('should reject reserved IETF namespace', async () => { + await expect( + client.exchangeToken({ + ...baseParams, + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', + }) + ).rejects.toThrow('Reserved namespaces are prohibited'); + }); + + test('should reject Auth0.com namespace', async () => { + await expect( + client.exchangeToken({ + ...baseParams, + subject_token_type: 'https://auth0.com/custom-token', + }) + ).rejects.toThrow('Reserved namespaces are prohibited'); + }); + + test('should handle consent_required error', async () => { + nock(`https://${DOMAIN}`).post('/oauth/token').reply(400, { + error: 'invalid_request', + error_description: 'Consent required', + }); + + await expect(client.exchangeToken(baseParams)).rejects.toThrow('Consent required'); + }); + + test('should handle rate limiting', async () => { + nock(`https://${DOMAIN}`).post('/oauth/token').reply(429, { + error: 'too_many_attempts', + error_description: 'Too many requests - try again later', + }); + + await expect(client.exchangeToken(baseParams)).rejects.toThrow('Too many requests'); + }); + + test('should handle invalid credentials', async () => { + nock(`https://${DOMAIN}`).post('/oauth/token').reply(401, { + error: 'invalid_client', + error_description: 'Invalid client credentials', + }); + + await expect(client.exchangeToken(baseParams)).rejects.toThrow('Invalid client credentials'); + }); + + test('should forward custom parameters', async () => { + nock(`https://${DOMAIN}`) + .post('/oauth/token', /custom_param=value/) + .reply(200, { access_token: '...', id_token: '...', expires_in: 3600 }); + + await client.exchangeToken({ + ...baseParams, + custom_param: 'value', + }); + }); + + test('should validate token type before making request', async () => { + // No nock setup - should fail before making request + await expect( + client.exchangeToken({ + ...baseParams, + subject_token_type: 'urn:auth0:invalid-type', + }) + ).rejects.toThrow('Reserved namespaces are prohibited'); + }); + }); + + describe('validateTokenType()', () => { + const validCases = [ + 'urn:company:custom-token', + 'https://api.company.com/token-type', + 'urn:partner:legacy-system:v1', + ]; + + const invalidCases = [ + { type: 'urn:ietf:params:oauth:jwt', reason: 'IETF namespace' }, + { type: 'https://auth0.com/token', reason: 'Auth0 root domain' }, + { type: 'urn:auth0:custom-type', reason: 'Auth0 URN' }, + ]; + + validCases.forEach((tokenType) => { + test(`should allow ${tokenType}`, () => { + expect(() => (client as any).validateTokenType(tokenType)).not.toThrow(); + }); + }); + + invalidCases.forEach(({ type, reason }) => { + test(`should reject ${reason} (${type})`, () => { + expect(() => (client as any).validateTokenType(type)).toThrow(/Reserved namespaces/); + }); + }); + }); +}); From 95b36c7e574c289e327909af06cadc3dbe13cbef Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Mon, 27 Jan 2025 22:05:44 +0530 Subject: [PATCH 2/3] custom token exchange management api changes --- src/management/__generated/index.ts | 2 + src/management/__generated/managers/index.ts | 1 + .../token-exchange-profiles-manager.ts | 175 ++++++++++++++++++ src/management/__generated/models/index.ts | 151 +++++++++++++++ test/management/customTokenExchange.test.ts | 74 ++++++++ 5 files changed, 403 insertions(+) create mode 100644 src/management/__generated/managers/token-exchange-profiles-manager.ts create mode 100644 test/management/customTokenExchange.test.ts diff --git a/src/management/__generated/index.ts b/src/management/__generated/index.ts index ffa6462e2..5572533f0 100644 --- a/src/management/__generated/index.ts +++ b/src/management/__generated/index.ts @@ -35,6 +35,7 @@ import { StatsManager, TenantsManager, TicketsManager, + TokenExchangeProfilesManager, UserBlocksManager, UsersManager, UsersByEmailManager, @@ -76,6 +77,7 @@ export abstract class ManagementClientBase { public readonly stats = new StatsManager(this.configuration); public readonly tenants = new TenantsManager(this.configuration); public readonly tickets = new TicketsManager(this.configuration); + public readonly tokenExchangeProfiles = new TokenExchangeProfilesManager(this.configuration); public readonly userBlocks = new UserBlocksManager(this.configuration); public readonly users = new UsersManager(this.configuration); public readonly usersByEmail = new UsersByEmailManager(this.configuration); diff --git a/src/management/__generated/managers/index.ts b/src/management/__generated/managers/index.ts index 9b417e017..0552ce401 100644 --- a/src/management/__generated/managers/index.ts +++ b/src/management/__generated/managers/index.ts @@ -31,6 +31,7 @@ export * from './sessions-manager.js'; export * from './stats-manager.js'; export * from './tenants-manager.js'; export * from './tickets-manager.js'; +export * from './token-exchange-profiles-manager.js'; export * from './user-blocks-manager.js'; export * from './users-manager.js'; export * from './users-by-email-manager.js'; diff --git a/src/management/__generated/managers/token-exchange-profiles-manager.ts b/src/management/__generated/managers/token-exchange-profiles-manager.ts new file mode 100644 index 000000000..99bba266e --- /dev/null +++ b/src/management/__generated/managers/token-exchange-profiles-manager.ts @@ -0,0 +1,175 @@ +import * as runtime from '../../../lib/runtime.js'; +import type { InitOverride, ApiResponse } from '../../../lib/runtime.js'; +import type { + GetTokenExchangeProfiles200Response, + GetTokenExchangeProfilesById200Response, + PatchTokenExchangeProfilesByIdRequest, + PostTokenExchangeProfilesRequest, + DeleteTokenExchangeProfilesByIdRequest, + GetTokenExchangeProfilesRequest, + GetTokenExchangeProfilesByIdRequest, + PatchTokenExchangeProfilesByIdOperationRequest, +} from '../models/index.js'; + +const { BaseAPI } = runtime; + +/** + * + */ +export class TokenExchangeProfilesManager extends BaseAPI { + /** + * Delete a Token Exchange Profile within your tenant. + * Delete a token exchange profile + * + * @throws {RequiredError} + */ + async delete( + requestParameters: DeleteTokenExchangeProfilesByIdRequest, + initOverrides?: InitOverride + ): Promise> { + runtime.validateRequiredRequestParams(requestParameters, ['id']); + + const response = await this.request( + { + path: `/token-exchange-profiles/{id}`.replace( + '{id}', + encodeURIComponent(String(requestParameters.id)) + ), + method: 'DELETE', + }, + initOverrides + ); + + return runtime.VoidApiResponse.fromResponse(response); + } + + /** + * Retrieve a list of all Token Exchange Profiles available in your tenant. + * + * This endpoint supports Checkpoint pagination. To search by checkpoint, use the following parameters: + *
    + *
  • from: Optional id from which to start selection.
  • + *
  • take: The total amount of entries to retrieve when using the from parameter. Defaults to 50.
  • + *
+ * + * Note: The first time you call this endpoint using checkpoint pagination, omit the from parameter. If there are more results, a next value is included in the response. You can use this for subsequent API calls. When next is no longer included in the response, no pages are remaining. + * Get token exchange profiles + * + * @throws {RequiredError} + */ + async getAll( + requestParameters: GetTokenExchangeProfilesRequest = {}, + initOverrides?: InitOverride + ): Promise> { + const queryParameters = runtime.applyQueryParams(requestParameters, [ + { + key: 'from', + config: {}, + }, + { + key: 'take', + config: {}, + }, + ]); + + const response = await this.request( + { + path: `/token-exchange-profiles`, + method: 'GET', + query: queryParameters, + }, + initOverrides + ); + + return runtime.JSONApiResponse.fromResponse(response); + } + + /** + * Retrieve details about a single Token Exchange Profile specified by ID. + * + * Get a token exchange profile + * + * @throws {RequiredError} + */ + async get( + requestParameters: GetTokenExchangeProfilesByIdRequest, + initOverrides?: InitOverride + ): Promise> { + runtime.validateRequiredRequestParams(requestParameters, ['id']); + + const response = await this.request( + { + path: `/token-exchange-profiles/{id}`.replace( + '{id}', + encodeURIComponent(String(requestParameters.id)) + ), + method: 'GET', + }, + initOverrides + ); + + return runtime.JSONApiResponse.fromResponse(response); + } + + /** + * Update a Token Exchange Profile within your tenant. + * + * Update an existing token exchange profile + * + * @throws {RequiredError} + */ + async update( + requestParameters: PatchTokenExchangeProfilesByIdOperationRequest, + bodyParameters: PatchTokenExchangeProfilesByIdRequest, + initOverrides?: InitOverride + ): Promise> { + runtime.validateRequiredRequestParams(requestParameters, ['id']); + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + const response = await this.request( + { + path: `/token-exchange-profiles/{id}`.replace( + '{id}', + encodeURIComponent(String(requestParameters.id)) + ), + method: 'PATCH', + headers: headerParameters, + body: bodyParameters, + }, + initOverrides + ); + + return runtime.VoidApiResponse.fromResponse(response); + } + + /** + * Create a new Token Exchange Profile within your tenant. + * + * Create a token exchange profile + * + * @throws {RequiredError} + */ + async create( + bodyParameters: PostTokenExchangeProfilesRequest, + initOverrides?: InitOverride + ): Promise> { + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + const response = await this.request( + { + path: `/token-exchange-profiles`, + method: 'POST', + headers: headerParameters, + body: bodyParameters, + }, + initOverrides + ); + + return runtime.JSONApiResponse.fromResponse(response); + } +} diff --git a/src/management/__generated/models/index.ts b/src/management/__generated/models/index.ts index ae7372cdd..60cdd9865 100644 --- a/src/management/__generated/models/index.ts +++ b/src/management/__generated/models/index.ts @@ -7959,6 +7959,65 @@ export interface GetSuspiciousIpThrottling200ResponseStagePreUserRegistration { */ rate: number; } +/** + * + */ +export interface GetTokenExchangeProfiles200Response { + /** + */ + pagination: { [key: string]: any }; + /** + */ + token_exchange_profiles: Array; +} +/** + * + */ +export interface GetTokenExchangeProfilesById200Response { + [key: string]: any | any; + /** + * The unique ID of the token exchange profile. + * + */ + id: string; + /** + * Friendly name of this profile. + * + */ + name: string; + /** + * Subject token type for this profile. When receiving a token exchange request on the Authentication API, the corresponding token exchange profile with a matching subject_token_type will be executed. This must be a URI. + * + */ + subject_token_type: string; + /** + * The ID of the Custom Token Exchange action to execute for this profile, in order to validate the subject_token. The action must use the custom-token-exchange trigger. + * + */ + action_id: string; + /** + * The type of the profile, which controls how the profile will be executed when receiving a token exchange request. + * + */ + type: GetTokenExchangeProfilesById200ResponseTypeEnum; + /** + * The time when this profile was created. + * + */ + created_at: string; + /** + * The time when this profile was updated. + * + */ + updated_at: string; +} + +export const GetTokenExchangeProfilesById200ResponseTypeEnum = { + custom_authentication: 'custom_authentication', +} as const; +export type GetTokenExchangeProfilesById200ResponseTypeEnum = + (typeof GetTokenExchangeProfilesById200ResponseTypeEnum)[keyof typeof GetTokenExchangeProfilesById200ResponseTypeEnum]; + /** * */ @@ -9414,6 +9473,21 @@ export interface PatchSuspiciousIpThrottlingRequestStagePreUserRegistration { */ rate?: number; } +/** + * + */ +export interface PatchTokenExchangeProfilesByIdRequest { + /** + * Friendly name of this profile. + * + */ + name?: string; + /** + * Subject token type for this profile. When receiving a token exchange request on the Authentication API, the corresponding token exchange profile with a matching subject_token_type will be executed. This must be a URI. + * + */ + subject_token_type?: string; +} /** * */ @@ -13643,6 +13717,38 @@ export interface PostTicket200Response { */ ticket_url: string; } +/** + * + */ +export interface PostTokenExchangeProfilesRequest { + /** + * Friendly name of this profile. + * + */ + name: string; + /** + * Subject token type for this profile. When receiving a token exchange request on the Authentication API, the corresponding token exchange profile with a matching subject_token_type will be executed. This must be a URI. + * + */ + subject_token_type: string; + /** + * The ID of the Custom Token Exchange action to execute for this profile, in order to validate the subject_token. The action must use the custom-token-exchange trigger. + * + */ + action_id: string; + /** + * The type of the profile, which controls how the profile will be executed when receiving a token exchange request. + * + */ + type: PostTokenExchangeProfilesRequestTypeEnum; +} + +export const PostTokenExchangeProfilesRequestTypeEnum = { + custom_authentication: 'custom_authentication', +} as const; +export type PostTokenExchangeProfilesRequestTypeEnum = + (typeof PostTokenExchangeProfilesRequestTypeEnum)[keyof typeof PostTokenExchangeProfilesRequestTypeEnum]; + /** * */ @@ -19568,6 +19674,51 @@ export interface TenantSettingsRouteRequest { */ include_fields?: boolean; } +/** + * + */ +export interface DeleteTokenExchangeProfilesByIdRequest { + /** + * ID of the Token Exchange Profile to delete. + * + */ + id: string; +} +/** + * + */ +export interface GetTokenExchangeProfilesRequest { + /** + * Optional Id from which to start selection. + * + */ + from?: string; + /** + * Number of results per page. Defaults to 50. + * + */ + take?: number; +} +/** + * + */ +export interface GetTokenExchangeProfilesByIdRequest { + /** + * ID of the Token Exchange Profile to retrieve. + * + */ + id: string; +} +/** + * + */ +export interface PatchTokenExchangeProfilesByIdOperationRequest { + /** + * ID of the Token Exchange Profile to update. + * + */ + id: string; +} /** * */ diff --git a/test/management/customTokenExchange.test.ts b/test/management/customTokenExchange.test.ts new file mode 100644 index 000000000..fc7311dd3 --- /dev/null +++ b/test/management/customTokenExchange.test.ts @@ -0,0 +1,74 @@ +import { + ManagementClient, + TokenExchangeProfilesManager, + PostTokenExchangeProfilesRequest, + PatchTokenExchangeProfilesByIdRequest, +} from '../../src/index.js'; +import { checkMethod } from './tests.util.js'; + +const DOMAIN = `tenant.auth0.com`; +const token = 'TOKEN'; + +describe('tokenExchangeProfilesManager', () => { + const tokenExchangeProfilesManager: TokenExchangeProfilesManager = new ManagementClient({ + domain: DOMAIN, + token, + }).tokenExchangeProfiles; + + const dummyCreateBody: PostTokenExchangeProfilesRequest = { + name: 'profileName', + subject_token_type: 'urn:acme:legacy-system-token', + action_id: 'actionId', + type: 'custom_authentication', + }; + + const dummyUpdateBody: PatchTokenExchangeProfilesByIdRequest = { + name: 'profileName', + subject_token_type: 'urn:acme:legacy-system-token1', + }; + + // this is the test for the method tokenExchangeProfilesManager.create + describe('create', () => { + const operation = tokenExchangeProfilesManager.create(dummyCreateBody); + const uri = `/token-exchange-profiles`; + const method = 'post'; + + checkMethod({ operation, uri, method }); + }); + + // this is the test for the method tokenExchangeProfilesManager.update + describe('update', () => { + const operation = tokenExchangeProfilesManager.update({ id: 'profile1' }, dummyUpdateBody); + const uri = `/token-exchange-profiles/profile1`; + const method = 'patch'; + + checkMethod({ operation, uri, method }); + }); + + // this is the test for the method tokenExchangeProfilesManager.delete + describe('delete', () => { + const operation = tokenExchangeProfilesManager.delete({ id: 'profile1' }); + const uri = `/token-exchange-profiles/profile1`; + const method = 'delete'; + + checkMethod({ operation, uri, method }); + }); + + // this is the test for the method tokenExchangeProfilesManager.get + describe('get', () => { + const operation = tokenExchangeProfilesManager.get({ id: 'profile1' }); + const uri = `/token-exchange-profiles/profile1`; + const method = 'get'; + + checkMethod({ operation, uri, method }); + }); + + // this is the test for the method tokenExchangeProfilesManager.getAll + describe('getAll', () => { + const operation = tokenExchangeProfilesManager.getAll(); + const uri = `/token-exchange-profiles`; + const method = 'get'; + + checkMethod({ operation, uri, method }); + }); +}); From 9f8f2b7f8a8ad394922d8524ec6b400c550452be Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Mon, 27 Jan 2025 22:06:09 +0530 Subject: [PATCH 3/3] remove lodash-es from package-lock.json --- package-lock.json | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85c72a8ba..75e4b3dd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,11 @@ "license": "MIT", "dependencies": { "jose": "^4.13.2", - "lodash-es": "^4.17.21", "undici-types": "^6.15.0", "uuid": "^9.0.0" }, "devDependencies": { "@types/jest": "^29.5.0", - "@types/lodash-es": "^4.17.12", "@types/node": "^16.18.37", "@types/node-fetch": "^2.6.3", "@types/uuid": "^9.0.1", @@ -1464,21 +1462,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", - "dev": true - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/node": { "version": "16.18.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.37.tgz", @@ -5236,11 +5219,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8681,21 +8659,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", - "dev": true - }, - "@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, "@types/node": { "version": "16.18.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.37.tgz", @@ -11452,11 +11415,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",