Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
37 changes: 37 additions & 0 deletions docs/configuring-the-deploy-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,41 @@ String. Specifies the JWT signing algorithms used by the client when facilitatin

Boolean. When enabled, will allow the tool to delete resources. Default: `false`.

### `AUTH0_INCLUDED_CONNECTIONS`

Array of strings. Specifies which connections should be managed by the Deploy CLI. When configured, only the connections listed by name will be included in export and import operations. All other connections in the tenant will be completely ignored.

This is particularly useful for:
- Managing only specific connections while preserving others (e.g., self-service SSO connections, third-party integrations)
- Preventing accidental modifications to connections managed by other systems
- Isolating connection management to specific subsets of your tenant

**Important:** This setting affects all operations (export and import). Connections not in this list will not appear in exports and will not be modified during imports.

Cannot be used simultaneously with `AUTH0_EXCLUDED_CONNECTIONS`.

#### Example

```json
{
"AUTH0_INCLUDED_CONNECTIONS": ["github", "google-oauth2"]
}
```

In the example above, only the `github` and `google-oauth2` connections will be managed. All other connections in the tenant will be ignored.

#### Environment Variable Format

When passing as an environment variable, use JSON array format:

```shell
# JSON array format
export AUTH0_INCLUDED_CONNECTIONS='["github","google-oauth2","Username-Password-Authentication"]'

# Or as a single-line array
export AUTH0_INCLUDED_CONNECTIONS='["github"]'
```

### `AUTH0_EXCLUDED`

Array of strings. Excludes entire resource types from being managed, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `phoneProviders`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
Expand Down Expand Up @@ -175,6 +210,8 @@ Array of strings. Excludes the management of specific databases by name. **Note:

Array of strings. Excludes the management of specific connections by name. **Note:** This configuration may be subject to deprecation in the future. See: [excluding resources from management](excluding-from-management.md).

Cannot be used simultaneously with `AUTH0_INCLUDED_CONNECTIONS`.

### `AUTH0_EXCLUDED_RESOURCE_SERVERS`

Array of strings. Excludes the management of specific resource servers by name. **Note:** This configuration may be subject to deprecation in the future. See: [excluding resources from management](excluding-from-management.md).
Expand Down
4 changes: 4 additions & 0 deletions src/context/directory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export default class DirectoryContext {
resourceServers: config.AUTH0_EXCLUDED_RESOURCE_SERVERS || [],
defaults: config.AUTH0_EXCLUDED_DEFAULTS || [],
};

this.assets.include = {
connections: config.AUTH0_INCLUDED_CONNECTIONS || [],
};
}

loadFile(f: string, folder: string) {
Expand Down
16 changes: 16 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const nonPrimitiveProps: (keyof Config)[] = [
'AUTH0_INCLUDED_ONLY',
'EXCLUDED_PROPS',
'INCLUDED_PROPS',
'AUTH0_INCLUDED_CONNECTIONS',
];

const EA_FEATURES = [];
Expand Down Expand Up @@ -134,6 +135,21 @@ export const setupContext = async (
}
})(config);

((config: Config) => {
const hasIncludedConnections =
config.AUTH0_INCLUDED_CONNECTIONS !== undefined &&
config.AUTH0_INCLUDED_CONNECTIONS.length > 0;
const hasExcludedConnections =
config.AUTH0_EXCLUDED_CONNECTIONS !== undefined &&
config.AUTH0_EXCLUDED_CONNECTIONS.length > 0;

if (hasIncludedConnections && hasExcludedConnections) {
throw new Error(
'Both AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS configuration values are defined, only one can be configured at a time.'
);
}
})(config);

((config: Config) => {
// Check if experimental early access features are enabled
if (config.AUTH0_EXPERIMENTAL_EA) {
Expand Down
5 changes: 5 additions & 0 deletions src/context/yaml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export default class YAMLContext {
defaults: config.AUTH0_EXCLUDED_DEFAULTS || [],
};

this.assets.include = {
connections: config.AUTH0_INCLUDED_CONNECTIONS || [],
};

this.basePath = (() => {
if (!!config.AUTH0_BASE_PATH) return config.AUTH0_BASE_PATH;
//@ts-ignore because this looks to be a bug, but do not want to introduce regression; more investigation needed
Expand Down Expand Up @@ -202,6 +206,7 @@ export default class YAMLContext {

// Delete exclude as it's not part of the auth0 tenant config
delete cleaned.exclude;
delete cleaned.include;

// Optionally Strip identifiers
if (!this.config.AUTH0_EXPORT_IDENTIFIERS) {
Expand Down
8 changes: 7 additions & 1 deletion src/tools/auth0/handlers/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,12 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
conflicts: [],
};

const includedConnections = (assets.include && assets.include.connections) || [];
const filteredConnections =
includedConnections.length > 0
? connections.filter((conn) => includedConnections.includes(conn.name))
: connections;

// Convert enabled_clients by name to the id
const clients = await paginate<Client>(this.client.clients.list, {
paginate: true,
Expand All @@ -616,7 +622,7 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
// Prepare an id map. We'll use this map later to get the `strategy` and SCIM enable status of the connections.
await this.scimHandler.createIdMap(existingConnections);

const formatted = connections.map((connection) => ({
const formatted = filteredConnections.map((connection) => ({
...connection,
...this.getFormattedOptions(connection, clients),
enabled_clients: getEnabledClients(assets, connection, existingConnections, clients),
Expand Down
7 changes: 6 additions & 1 deletion src/tools/auth0/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,10 @@ const auth0ApiHandlers: { [key in AssetTypes]: any } = {
};

export default auth0ApiHandlers as {
[key in AssetTypes]: { default: typeof APIHandler; excludeSchema?: any; schema: any };
[key in AssetTypes]: {
default: typeof APIHandler;
excludeSchema?: any;
schema: any;
includeSchema?: any;
};
}; // TODO: apply stronger types to schema properties
15 changes: 15 additions & 0 deletions src/tools/auth0/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ const excludeSchema = Object.entries(handlers).reduce(
{}
);

const includeSchema = Object.entries(handlers).reduce(
(map: { [key: string]: Object }, [name, obj]) => {
if (obj.includeSchema) {
map[name] = obj.includeSchema;
}
return map;
},
{}
);

export default {
type: 'object',
$schema: 'http://json-schema.org/draft-07/schema#',
Expand All @@ -28,6 +38,11 @@ export default {
properties: { ...excludeSchema },
default: {},
},
include: {
type: 'object',
properties: { ...includeSchema },
default: {},
},
},
additionalProperties: false,
};
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export type Config = {
INCLUDED_PROPS?: {
[key: string]: string[];
};
AUTH0_INCLUDED_CONNECTIONS?: string[];
AUTH0_IGNORE_UNAVAILABLE_MIGRATIONS?: boolean;
// Eventually deprecate. See: https://github.com/auth0/auth0-deploy-cli/issues/451#user-content-deprecated-exclusion-props
AUTH0_EXCLUDED_RULES?: string[];
Expand Down Expand Up @@ -138,6 +139,9 @@ export type Assets = Partial<{
exclude?: {
[key: string]: string[];
};
include?: {
[key: string]: string[];
};
clientsOrig: Asset[] | null;
themes: Theme[] | null;
forms: Form[] | null;
Expand Down
43 changes: 43 additions & 0 deletions test/context/context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,49 @@ describe('#context loader validation', async () => {
'Need to define at least one resource type in AUTH0_INCLUDED_ONLY configuration. See: https://github.com/auth0/auth0-deploy-cli/blob/master/docs/configuring-the-deploy-cli.md#auth0_included_only'
);
});

it('should error if trying to configure AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS simultaneously', async () => {
const dir = path.resolve(testDataDir, 'context');
cleanThenMkdir(dir);
const yaml = path.join(dir, 'empty.yaml');
fs.writeFileSync(yaml, '');

await expect(
setupContext(
{
...config,
AUTH0_INPUT_FILE: yaml,
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
AUTH0_EXCLUDED_CONNECTIONS: ['facebook'],
},
'import'
)
).to.be.rejectedWith(
'Both AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS configuration values are defined, only one can be configured at a time.'
);

await expect(
setupContext(
{
...config,
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
AUTH0_INPUT_FILE: yaml,
},
'import'
)
).to.be.not.rejected;

await expect(
setupContext(
{
...config,
AUTH0_EXCLUDED_CONNECTIONS: ['facebook'],
AUTH0_INPUT_FILE: yaml,
},
'import'
)
).to.be.not.rejected;
});
});

describe('#filterOnlyIncludedResourceTypes', async () => {
Expand Down
20 changes: 19 additions & 1 deletion test/context/directory/context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('#directory context validation', () => {
const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient());
await context.loadAssetsFromLocal();

expect(Object.keys(context.assets).length).to.equal(Object.keys(handlers).length + 1);
expect(Object.keys(context.assets).length).to.equal(Object.keys(handlers).length + 2);
Object.keys(context.assets).forEach((key) => {
if (key === 'exclude') {
expect(context.assets[key]).to.deep.equal({
Expand All @@ -26,6 +26,10 @@ describe('#directory context validation', () => {
resourceServers: [],
defaults: [],
});
} else if (key === 'include') {
expect(context.assets[key]).to.deep.equal({
connections: [],
});
} else {
expect(context.assets[key]).to.equal(null);
}
Expand Down Expand Up @@ -57,6 +61,20 @@ describe('#directory context validation', () => {
expect(context.assets.exclude.defaults).to.deep.equal(['emailProvider']);
});

it('should load includes', async () => {
const dir = path.resolve(testDataDir, 'directory', 'empty');
cleanThenMkdir(dir);

const config = {
AUTH0_INPUT_FILE: dir,
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
};
const context = new Context(config, mockMgmtClient());
await context.loadAssetsFromLocal();

expect(context.assets.include.connections).to.deep.equal(['github', 'google-oauth2']);
});

it('should respect resource exclusion on import', async () => {
const tenantConfig = {
allowed_logout_urls: ['https://mycompany.org/logoutCallback'],
Expand Down
17 changes: 17 additions & 0 deletions test/context/yaml/context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ describe('#YAML context validation', () => {
expect(context.assets.exclude.defaults).to.deep.equal(['emailProvider']);
});

it('should load includes', async () => {
const dir = path.resolve(testDataDir, 'yaml', 'empty');
cleanThenMkdir(dir);
const yaml = path.join(dir, 'empty.yaml');
fs.writeFileSync(yaml, '');

const config = {
AUTH0_INPUT_FILE: yaml,
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
};

const context = new Context(config, mockMgmtClient());
await context.loadAssetsFromLocal();

expect(context.assets.include.connections).to.deep.equal(['github', 'google-oauth2']);
});

it('should respect resource exclusion on import', async () => {
/* Create empty directory */
const dir = path.resolve(testDataDir, 'yaml', 'resource-exclusion');
Expand Down
Loading