Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
159 commits
Select commit Hold shift + click to select a range
d5913de
[WorkplaceAI] Add authz code-grant flow in Stack Connectors V2 (#246655)
seanstory Feb 4, 2026
01cf3be
[ResponseOps][Connectors] Add auth mode to auth types (#252307)
jcger Feb 9, 2026
c989b64
Merge branch 'main' of https://github.com/jcger/kibana into connector…
jcger Feb 9, 2026
6ae9569
[Workplace AI][PerUserAuth] Support connectors created in non default…
lorenabalan Feb 10, 2026
52cc7e8
[Workplace AI][PerUserAuth] Escape callback html and oauth state clie…
lorenabalan Feb 10, 2026
b8f0483
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 10, 2026
2865111
[ResponseOps][Connectors] Add authMode to connector saved objects and…
js-jankisalvi Feb 12, 2026
f1e5cf2
[WorkplaceAI][PerUserAuth] Remove `redirectUri` from `oauth_state` SO…
lorenabalan Feb 12, 2026
a63cbfd
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 16, 2026
fe99023
[WorkplaceAI][PerUserAuth] Add unit tests for OAuth Code Grant Flow (…
lorenabalan Feb 17, 2026
bfa8e7f
[ResponseOps][Connectors] Add authentication column to connectors tab…
js-jankisalvi Feb 17, 2026
7bc6b62
[Response Ops][Connectors] Add new user token SO (#252501)
jcger Feb 18, 2026
83c0328
Init commit
lorenabalan Feb 18, 2026
da1e500
Merge branch 'main' into connectors-auth-code-grant
dennis-tismenko Feb 18, 2026
846c86b
Add missing unit tests
lorenabalan Feb 18, 2026
6776339
Refactor common code and update unit tests
lorenabalan Feb 18, 2026
8041b7d
Get auth type from secrets
lorenabalan Feb 18, 2026
f393e79
Enable EARS auth on Google connector
lorenabalan Feb 18, 2026
c3ce4da
Enable Authorize button in the UI
lorenabalan Feb 18, 2026
49b890e
Bug fixes
lorenabalan Feb 19, 2026
df98a36
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 20, 2026
6a19640
Fix OAuth State cleanup task
lorenabalan Feb 20, 2026
5b38f89
Set EARS base URL from config
lorenabalan Feb 20, 2026
5b7af60
Only specify provider in connector input
lorenabalan Feb 20, 2026
67c523c
Fix refresh logic
lorenabalan Feb 20, 2026
83491d4
Merge and fix conflicts
lorenabalan Feb 23, 2026
625d85a
Lint
lorenabalan Feb 23, 2026
b2412b2
Pass actual id to refresh flow as well
lorenabalan Feb 23, 2026
b5f4bf4
[WorkplaceAI][PerUserAuth] Add hooks for connector code grant OAuth f…
dennis-tismenko Feb 23, 2026
1b86769
Merge branch 'main' into connectors-auth-code-grant
dennis-tismenko Feb 23, 2026
b403663
Changes from node scripts/generate codeowners
kibanamachine Feb 23, 2026
f004982
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Feb 23, 2026
25a6409
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Feb 24, 2026
c942aef
Small fixes (#254557)
lorenabalan Feb 24, 2026
a4b08c0
fix SO count test + docs
jcger Feb 24, 2026
460ebf7
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Feb 24, 2026
e8bcea2
Changes from make api-docs
kibanamachine Feb 24, 2026
e7e0dd0
add xpack.actions.auth.oauth_authorization_code.enabled flag
jcger Feb 24, 2026
2fd16c9
Changes from make api-docs
kibanamachine Feb 25, 2026
a47ef6d
FF effect on SO and authMode in GET connectors
jcger Feb 25, 2026
1207799
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Feb 25, 2026
82a2597
[ResponseOps][Connectors] Use profileUid in actions client (#254589)
jcger Feb 25, 2026
92b7bb4
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 25, 2026
3a192f0
fix tests
jcger Feb 25, 2026
354bd23
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Feb 25, 2026
a386360
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Feb 25, 2026
abff0b5
reset core + docs
jcger Feb 25, 2026
ff13481
Changes from make api-docs
kibanamachine Feb 25, 2026
bf90b46
[ResponseOps][PerUserAuth] Delete user tokens on connector delete and…
js-jankisalvi Feb 25, 2026
ac6e2da
Changes from node scripts/jest_integration -u src/core/server/integra…
kibanamachine Feb 25, 2026
b1c1a81
Merge and fix conflicts, best effort
lorenabalan Feb 25, 2026
f2269a5
Post-merge fixes
lorenabalan Feb 25, 2026
0f3eb62
conditional schema models
jcger Feb 26, 2026
bc79bda
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 26, 2026
e3bb9cd
[WorkplaceAI][PerUserAuth] Implement Encrypted Saved Objects recommen…
lorenabalan Feb 26, 2026
3bca053
Merge branch 'connectors-auth-code-grant' into lb/poc/ears-auth
lorenabalan Feb 26, 2026
78e721d
remove feature flag
jcger Feb 27, 2026
8d27c46
Changes from node scripts/check_mappings_update --fix
kibanamachine Feb 27, 2026
b40260b
Changes from make api-docs
kibanamachine Feb 27, 2026
bf8164d
update SO counter
jcger Feb 27, 2026
a8174bf
Merge branch 'lb/poc/ears-auth' of github.com:elastic/kibana into pr-…
jcger Feb 27, 2026
c13e3f9
set xpack.workplaceAIApp.ears.url flag
jcger Feb 27, 2026
b6d2faa
Changes from node scripts/jest_integration -u src/core/server/integra…
kibanamachine Feb 27, 2026
9b5b5f1
Fix; check for user tokens instead of shared ones
lorenabalan Feb 27, 2026
2c7d0e4
Merge branch 'lb/poc/ears-auth' of github.com:elastic/kibana into lb/…
lorenabalan Feb 27, 2026
2fe0512
fix tests
jcger Feb 27, 2026
fd873d4
remove kbn setting
jcger Feb 27, 2026
0eab256
trigger deployment, empty commit
lorenabalan Feb 27, 2026
e15fdb6
Merge branch 'lb/poc/ears-auth' of github.com:elastic/kibana into lb/…
lorenabalan Feb 27, 2026
ada770b
Merge branch 'main' into connectors-auth-code-grant
jcger Feb 27, 2026
a16ff65
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Feb 27, 2026
6359806
wire wpai for label deploy
jcger Feb 27, 2026
4f3cd55
update SO count
jcger Feb 27, 2026
4cb6dff
EARS url in actions config
lorenabalan Feb 27, 2026
dd77ebe
Merge branch 'lb/poc/ears-auth' of github.com:elastic/kibana into lb/…
lorenabalan Feb 27, 2026
253a362
fix test
jcger Mar 2, 2026
fe98754
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Mar 5, 2026
638bef0
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 9, 2026
dcde963
[ResponseOps][Connectors] Authorization Code - Remove feature flag (…
jcger Mar 9, 2026
9818ab6
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 9, 2026
d7d1bfa
rollback limits + fix test
jcger Mar 9, 2026
02e131e
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Mar 9, 2026
15d2c19
rename auth rate limit kbn setting to
jcger Mar 9, 2026
8999b5c
add missing fixture
jcger Mar 9, 2026
fe05a44
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 10, 2026
3e36591
Undo CI pipeline changes for workplace_ai
lorenabalan Mar 10, 2026
66df421
Use discriminated union
lorenabalan Mar 10, 2026
44e1d47
removed unrelated code
jcger Mar 10, 2026
8081451
Reset lock mapping
lorenabalan Mar 10, 2026
d1c8066
Remove noisy type
lorenabalan Mar 10, 2026
a3ac3ae
fix test
jcger Mar 10, 2026
0b23084
Merge branch 'connectors-auth-code-grant' of github.com:elastic/kiban…
jcger Mar 10, 2026
3b58a2b
Merge branch 'connectors-auth-code-grant' into lb/poc/ears-auth
lorenabalan Mar 10, 2026
e72d8c3
Factor out duplicate code
lorenabalan Mar 10, 2026
1a59817
Isolate ears-related code
lorenabalan Mar 10, 2026
ce098bf
Refactor switches into one Strategy object
lorenabalan Mar 10, 2026
62c16cb
add missing authMode
jcger Mar 10, 2026
d136c1a
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 10, 2026
6ae2892
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Mar 11, 2026
1425dce
Fix tests
lorenabalan Mar 10, 2026
d0cf87b
Refactor resolveEarsUrl
lorenabalan Mar 11, 2026
cc836cb
Merge branch 'connectors-auth-code-grant' into lb/poc/ears-auth
lorenabalan Mar 11, 2026
5d57743
Fix bad merge
lorenabalan Mar 11, 2026
55dffe2
Simplify ears url config handling
lorenabalan Mar 11, 2026
08fbe1a
Fix bad merge again
lorenabalan Mar 11, 2026
3eb6448
Clean up how we derive endpoints
lorenabalan Mar 11, 2026
e767818
Fix tests and remove discriminated union
lorenabalan Mar 11, 2026
34cfe1b
More minor cleaning up
lorenabalan Mar 11, 2026
7582df0
Fix authz endpoints
lorenabalan Mar 12, 2026
34708e5
fix refresh token
jcger Mar 12, 2026
97288d3
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Mar 12, 2026
84cce1b
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 12, 2026
343bf64
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 12, 2026
22f6f7b
update moon files
jcger Mar 12, 2026
184a1b4
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Mar 12, 2026
b95ddbb
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Mar 13, 2026
2d7309d
Merge branch 'connectors-auth-code-grant' into lb/poc/ears-auth
lorenabalan Mar 13, 2026
6030c89
solve conflicts
jcger Mar 13, 2026
354c11c
Merge branch 'main' of github.com:jcger/kibana into connectors-auth-c…
jcger Mar 13, 2026
3f46049
remove logs
jcger Mar 13, 2026
81d9c25
add test and reenable basic auth for client_secret_basic
jcger Mar 13, 2026
3324000
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 16, 2026
798f72d
DRY providers and URL schema type
lorenabalan Mar 16, 2026
f8597ab
Merge branch 'main' into connectors-auth-code-grant
jcger Mar 16, 2026
a919e0b
Debug log and update docstring
lorenabalan Mar 16, 2026
5a560dd
Merge branch 'connectors-auth-code-grant' into lb/poc/ears-auth
lorenabalan Mar 16, 2026
dbb5ade
Update codeowners
lorenabalan Mar 16, 2026
a0ed2ea
Add README
lorenabalan Mar 16, 2026
7833754
Update imports
lorenabalan Mar 16, 2026
9a9e7a6
Update with EARS API version
lorenabalan Mar 16, 2026
0eeb9dd
Raise more generic error message
lorenabalan Mar 16, 2026
643e6e5
Fix tests
lorenabalan Mar 16, 2026
c98fc91
Fix bad merge
lorenabalan Mar 16, 2026
7cffe7c
Remove unused label
lorenabalan Mar 17, 2026
22e77aa
Add guard for connectorTokenClient
lorenabalan Mar 17, 2026
e509539
Put back discriminated union
lorenabalan Mar 17, 2026
89beff8
Remove leftover capitalize
lorenabalan Mar 17, 2026
f102ca7
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 19, 2026
3b960b4
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 19, 2026
f2408ee
Update readme with api version
lorenabalan Mar 19, 2026
09f6ae7
Update axiosInstance headers for future requests
lorenabalan Mar 19, 2026
b395e81
Fix useBasicAuth propagation
lorenabalan Mar 19, 2026
ea58466
Reset Google Drive connector
lorenabalan Mar 19, 2026
bd3da1a
Fix tests
lorenabalan Mar 19, 2026
268368a
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 19, 2026
5f8cfee
Update codeowners
lorenabalan Mar 19, 2026
a0102c8
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 20, 2026
957c16b
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 23, 2026
8640bdd
Key lock by connector ID and profile UID (#257968)
lorenabalan Mar 24, 2026
ef7cc7f
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 24, 2026
eae5bb4
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 27, 2026
e993ff9
Hmm fix validateOAuthConnector IDE complaint for now
lorenabalan Mar 27, 2026
c88f2e4
Fix - delete lock by lockKey; cover with unit tests
lorenabalan Mar 27, 2026
f3d24c3
Update label for consistency
lorenabalan Mar 27, 2026
32a8536
Check config too for authType
lorenabalan Mar 30, 2026
331e3a7
Remove dead code
lorenabalan Mar 30, 2026
c1c1dee
Check both secrets and config
lorenabalan Mar 30, 2026
318d461
Remove dead code from FE too
lorenabalan Mar 30, 2026
052d580
Merge branch 'main' into lb/poc/ears-auth
lorenabalan Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,12 @@ x-pack/platform/plugins/shared/inference_endpoint @elastic/search-kibana
/x-pack/platform/test/fixtures/es_archives/alerting/8_2_0 @elastic/response-ops
/x-pack/solutions/**/test/serverless/**/test_suites/rules/ @elastic/response-ops

# EARS
/x-pack/platform/plugins/shared/actions/server/lib/ears @elastic/workchat-eng
x-pack/platform/plugins/shared/actions/server/lib/axios_auth_strategies/ears_strategy.ts @elastic/workchat-eng @elastic/response-ops
x-pack/platform/plugins/shared/actions/server/lib/axios_auth_strategies/ears_strategy.test.ts @elastic/workchat-eng @elastic/response-ops
/src/platform/packages/shared/kbn-connector-specs/src/auth_types/ears.ts @elastic/workchat-eng

# Connector Specs
src/platform/packages/shared/kbn-connector-specs/src/all_specs.ts
src/platform/packages/shared/kbn-connector-specs/src/connector_icons_map.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * as connectorsSpecs from './src/all_specs';
export type * from './src/connector_spec';

export * as authTypeSpecs from './src/all_auth_types';
export { EARS_PROVIDERS } from './src/auth_types/ears';

export { getConnectorSpec } from './src/get_connector_spec';
export { getWorkflowTemplatesForConnector } from './src/get_workflow_templates';
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './auth_types/basic';
export * from './auth_types/none';
export * from './auth_types/oauth';
export * from './auth_types/oauth_authorization_code';
export { Ears } from './auth_types/ears';

// Skipping PFX and CRT exports for now as they will require updates to
// the formbuilder to support file upload fields.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { z } from '@kbn/zod/v4';
import type { AxiosInstance } from 'axios';
import type { AuthContext, AuthTypeSpec } from '../connector_spec';
import * as i18n from './translations';

export const EARS_PROVIDERS = ['google', 'microsoft', 'slack'] as const;

const authSchema = z
.object({
provider: z.enum(EARS_PROVIDERS).meta({ hidden: true }),
scope: z.string().meta({ label: i18n.OAUTH_SCOPE_LABEL }).optional(),
})
.meta({ label: i18n.EARS_LABEL });

type AuthSchemaType = z.infer<typeof authSchema>;

/**
* EARS (Elastic Authentication Redirect Service) OAuth Flow
*
* EARS is an OAuth proxy that manages client credentials (clientId/clientSecret)
* on behalf of the user. Instead of users creating their own OAuth apps for each
* 3rd party, they can rely on the Elastic-owned apps for simplicity.
* Therefore, connectors using EARS don't require users to input any
* client credentials — EARS already knows them.
*
* EARS Redirect Flow:
* 1. On `/_start_oauth_flow`, Kibana builds EARS authorize URL with callback_uri, state, scope, pkce_challenge, pkce_method, and redirects to it
* 2. User visits EARS authorize URL → EARS redirects to OAuth provider and shows auth screen to user, in order for them to enter their credentials and authorize scopes
* 3. OAuth provider redirects back to EARS with authz code & state
* 4. EARS redirects to callback_uri (Kibana's `/_oauth_callback`) with authz code & state
* 5. Kibana then exchanges code via EARS token endpoint: POST {earsTokenUrl} with code & pkce_verifier in the JSON body
* 6. Tokens are auto-refreshed when expired during connector execution
*/
export const Ears: AuthTypeSpec<AuthSchemaType> = {
id: 'ears',
schema: authSchema,
authMode: 'per-user',
configure: async (
ctx: AuthContext,
axiosInstance: AxiosInstance,
secret: AuthSchemaType
): Promise<AxiosInstance> => {
let token;
try {
token = await ctx.getToken({
authType: 'ears',
provider: secret.provider,
scope: secret.scope,
});
} catch (error) {
throw new Error(
`Unable to retrieve/refresh the access token. User may need to re-authorize: ${error.message}`
);
}

if (!token) {
throw new Error(`No access token available. User must complete OAuth authorization flow.`);
}

// set global defaults
axiosInstance.defaults.headers.common.Authorization = token;

return axiosInstance;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const OAuth: AuthTypeSpec<AuthSchemaType> = {
let token;
try {
token = await ctx.getToken({
authType: 'oauth',
tokenUrl: secret.tokenUrl,
scope: secret.scope,
clientId: secret.clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const OAuthAuthorizationCode: AuthTypeSpec<AuthSchemaType> = {
let token;
try {
token = await ctx.getToken({
authType: 'oauth',
tokenUrl: secret.tokenUrl,
scope: secret.scope,
clientId: secret.clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,7 @@ export const AWS_SECRET_ACCESS_KEY_REQUIRED_MESSAGE = i18n.translate(
defaultMessage: 'Secret Access Key is required',
}
);

export const EARS_LABEL = i18n.translate('connectorSpecs.ears.label', {
defaultMessage: 'OAuth 2.0 via Elastic-owned apps',
});
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export interface ConnectorMetadata {
// OAuth2, SSL/mTLS, AWS SigV4 → Phase 2 (see connector_rfc.ts)

// Auth schemas defined in ./auth_types
export interface GetTokenOpts {
export interface OAuthGetTokenOpts {
authType: 'oauth';
tokenUrl: string;
scope?: string;
clientId: string;
Expand All @@ -87,6 +88,14 @@ export interface GetTokenOpts {
tokenEndpointAuthMethod?: 'client_secret_post' | 'client_secret_basic';
}

export interface EarsGetTokenOpts {
authType: 'ears';
provider: string;
scope?: string;
}

export type GetTokenOpts = OAuthGetTokenOpts | EarsGetTokenOpts;

export interface AuthContext {
getCustomHostSettings: (url: string) => CustomHostSettings | undefined;
getToken: (opts: GetTokenOpts) => Promise<string | null>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const createActionsConfigMock = () => {
getAwsSesConfig: jest.fn().mockReturnValue(null),
getEnabledEmailServices: jest.fn().mockReturnValue(['*']),
getMaxEmailBodyLength: jest.fn().mockReturnValue(DEFAULT_EMAIL_BODY_LENGTH),
getEarsUrl: jest.fn().mockReturnValue(undefined),
};
return mocked;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const defaultActionsConfig: ActionsConfig = {
},
},
},
ears: {},
};

describe('ensureUriAllowed', () => {
Expand Down Expand Up @@ -770,6 +771,21 @@ describe('getAwsSesConfig()', () => {
});
});

describe('getEarsUrl()', () => {
test('returns undefined when ears.url is not set in config', () => {
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
expect(acu.getEarsUrl()).toBeUndefined();
});

test('returns the configured URL when ears.url is set in config', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
ears: { url: 'https://ears.example.com' },
});
expect(acu.getEarsUrl()).toBe('https://ears.example.com');
});
});

describe('getEnabledEmailServices()', () => {
test('returns all services when no email config set', () => {
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export interface ActionsConfigurationUtilities {
getAwsSesConfig: () => AwsSesConfig;
getEnabledEmailServices: () => string[];
getMaxEmailBodyLength: () => number;
getEarsUrl(): string | undefined;
}

function allowListErrorMessage(field: AllowListingField, value: string) {
Expand Down Expand Up @@ -283,5 +284,6 @@ export function getActionsConfigurationUtilities(
const nonNegativeLength = Math.max(0, configuredLength);
return Math.min(nonNegativeLength, MAX_EMAIL_BODY_LENGTH);
},
getEarsUrl: () => config.ears?.url,
};
}
5 changes: 5 additions & 0 deletions x-pack/platform/plugins/shared/actions/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ export const configSchema = schema.object({
rate_limits: oauthAuthorizationCodeRateLimitsSchema,
}),
}),
ears: schema.maybe(
schema.object({
url: schema.maybe(schema.uri({ scheme: ['https'] })),
})
),
});

export type ActionsConfig = TypeOf<typeof configSchema>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

jest.mock('../get_oauth_client_credentials_access_token');
jest.mock('../delete_token_axios_interceptor');

import type { AxiosInstance } from 'axios';
import type { GetTokenOpts, OAuthGetTokenOpts } from '@kbn/connector-specs';
import { loggerMock } from '@kbn/logging-mocks';
import { actionsConfigMock } from '../../actions_config.mock';
import { connectorTokenClientMock } from '../connector_token_client.mock';
import { getOAuthClientCredentialsAccessToken } from '../get_oauth_client_credentials_access_token';
import { getDeleteTokenAxiosInterceptor } from '../delete_token_axios_interceptor';
import { DefaultStrategy } from './default_strategy';
import type { AuthStrategyDeps } from './types';

const mockGetOAuthClientCredentialsAccessToken =
getOAuthClientCredentialsAccessToken as jest.MockedFunction<
typeof getOAuthClientCredentialsAccessToken
>;
const mockGetDeleteTokenAxiosInterceptor = getDeleteTokenAxiosInterceptor as jest.MockedFunction<
typeof getDeleteTokenAxiosInterceptor
>;

const logger = loggerMock.create();
const configurationUtilities = actionsConfigMock.create();
const connectorTokenClient = connectorTokenClientMock.create();

const baseDeps: AuthStrategyDeps = {
connectorId: 'connector-1',
secrets: {
clientId: 'my-client-id',
clientSecret: 'my-client-secret',
tokenUrl: 'https://provider.example.com/token',
scope: 'openid',
},
connectorTokenClient,
logger,
configurationUtilities,
};

const createMockAxiosInstance = () =>
({
interceptors: { response: { use: jest.fn() } },
} as unknown as AxiosInstance);

describe('DefaultStrategy', () => {
let strategy: DefaultStrategy;

const mockOnFulfilled = jest.fn();
const mockOnRejected = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
strategy = new DefaultStrategy();
mockGetDeleteTokenAxiosInterceptor.mockReturnValue({
onFulfilled: mockOnFulfilled,
onRejected: mockOnRejected,
});
});

describe('installResponseInterceptor', () => {
it('installs the delete-token cleanup interceptor', () => {
const instance = createMockAxiosInstance();
strategy.installResponseInterceptor(instance, baseDeps);

expect(mockGetDeleteTokenAxiosInterceptor).toHaveBeenCalledWith({
connectorTokenClient,
connectorId: 'connector-1',
});
expect(instance.interceptors.response.use).toHaveBeenCalledWith(
mockOnFulfilled,
mockOnRejected
);
});
});

describe('getToken', () => {
it('throws when opts authType is not oauth', async () => {
const opts: GetTokenOpts = { authType: 'ears', provider: 'google' };
await expect(strategy.getToken(opts, baseDeps)).rejects.toThrow(
'DefaultStrategy received non-oauth token opts'
);
});

it('delegates to getOAuthClientCredentialsAccessToken with correct args', async () => {
mockGetOAuthClientCredentialsAccessToken.mockResolvedValue('Bearer clientcred');

const opts: OAuthGetTokenOpts = {
authType: 'oauth',
tokenUrl: 'https://provider.example.com/token',
clientId: 'the-client-id',
clientSecret: 'the-client-secret',
scope: 'openid profile',
};
const result = await strategy.getToken(opts, baseDeps);

expect(result).toBe('Bearer clientcred');
expect(mockGetOAuthClientCredentialsAccessToken).toHaveBeenCalledWith(
expect.objectContaining({
connectorId: 'connector-1',
tokenUrl: 'https://provider.example.com/token',
oAuthScope: 'openid profile',
credentials: {
config: { clientId: 'the-client-id' },
secrets: { clientSecret: 'the-client-secret' },
},
connectorTokenClient,
})
);
});

it('includes additionalFields when present in opts', async () => {
mockGetOAuthClientCredentialsAccessToken.mockResolvedValue('Bearer token');

const opts: OAuthGetTokenOpts = {
authType: 'oauth',
tokenUrl: 'https://provider.example.com/token',
clientId: 'id',
clientSecret: 'secret',
additionalFields: { tenant: 'abc' },
};
await strategy.getToken(opts, baseDeps);

expect(mockGetOAuthClientCredentialsAccessToken).toHaveBeenCalledWith(
expect.objectContaining({
credentials: expect.objectContaining({
config: expect.objectContaining({ additionalFields: { tenant: 'abc' } }),
}),
})
);
});
});
});
Loading
Loading