Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/auth0/auth0-deploy-cli in…
Browse files Browse the repository at this point in the history
…to update-changelog-7.14.0
  • Loading branch information
Will Vedder committed Jun 27, 2022
2 parents 68ae23e + ac001f8 commit e4864ce
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 10 deletions.
42 changes: 41 additions & 1 deletion src/commands/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { deploy as toolsDeploy } from '../tools';
import log from '../logger';
import { setupContext } from '../context';
import { ImportParams } from '../args';
import { Assets, Config } from '../types';

export default async function importCMD(params: ImportParams) {
const {
Expand Down Expand Up @@ -48,8 +49,47 @@ export default async function importCMD(params: ImportParams) {
const config = configFactory();
config.setProvider((key) => nconf.get(key));

//@ts-ignore because context and assets still need to be typed TODO: type assets and type context
findUnreplacedKeywords(context.assets);

await toolsDeploy(context.assets, context.mgmtClient, config);

log.info('Import Successful');
}

export const findUnreplacedKeywords = (assets: Assets) => {
const recursiveFindUnreplacedKeywords = (target): string[] => {
let unreplaced: string[] = [];
if (target === undefined || target === null) return [];
if (Array.isArray(target)) {
target.forEach((child) => {
unreplaced.push(...recursiveFindUnreplacedKeywords(child));
});
} else if (typeof target === 'object') {
Object.values(target).forEach((child) => {
unreplaced.push(...recursiveFindUnreplacedKeywords(child));
});
}

if (typeof target === 'string') {
const arrayMatches = target.match(/(?<=@@).*(?=@@)/g);
if (arrayMatches !== null) {
return arrayMatches;
}
const keywordMatches = target.match(/(?<=##).*(?=##)/g);
if (keywordMatches !== null) {
return keywordMatches;
}
}

return unreplaced;
};

const unreplacedKeywords = recursiveFindUnreplacedKeywords(assets);

if (unreplacedKeywords.length > 0) {
throw `Unreplaced keywords found: ${unreplacedKeywords.join(
', '
)}. Either correct these values or add to AUTH0_KEYWORD_REPLACE_MAPPINGS configuration.`;
}
return;
};
2 changes: 1 addition & 1 deletion src/context/yaml/handlers/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ async function dump(context: YAMLContext): Promise<ParsedPages> {
// Dump html to file
const htmlFile = path.join(pagesFolder, `${page.name}.html`);
log.info(`Writing ${htmlFile}`);
fs.writeFileSync(htmlFile, page.html);
fs.writeFileSync(htmlFile, page.html || '');
return {
...page,
html: `./pages/${page.name}.html`,
Expand Down
73 changes: 69 additions & 4 deletions src/tools/auth0/handlers/tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@ import ValidationError from '../../validationError';
import DefaultHandler, { order } from './default';
import { supportedPages, pageNameMap } from './pages';
import { convertJsonToString } from '../../utils';
import { Asset, Assets } from '../../../types';
import { Asset, Assets, Language } from '../../../types';

export const schema = {
type: 'object',
};

export type Tenant = Asset & { enabled_locales: Language[]; flags: { [key: string]: boolean } };

const blockPageKeys = [
...Object.keys(pageNameMap),
...Object.values(pageNameMap),
...supportedPages,
];

export default class TenantHandler extends DefaultHandler {
existing: Tenant;

constructor(options: DefaultHandler) {
super({
...options,
Expand All @@ -25,6 +29,9 @@ export default class TenantHandler extends DefaultHandler {

async getType(): Promise<Asset> {
const tenant = await this.client.tenant.getSettings();

this.existing = tenant;

blockPageKeys.forEach((key) => {
if (tenant[key]) delete tenant[key];
});
Expand Down Expand Up @@ -53,10 +60,68 @@ export default class TenantHandler extends DefaultHandler {
async processChanges(assets: Assets): Promise<void> {
const { tenant } = assets;

if (tenant && Object.keys(tenant).length > 0) {
await this.client.tenant.updateSettings(tenant);
// Do nothing if not set
if (!tenant) return;

const existingTenant = this.existing || (await this.getType());

const updatedTenant = {
...tenant,
flags: sanitizeMigrationFlags({
existingFlags: existingTenant.flags,
proposedFlags: tenant.flags,
}),
};

if (updatedTenant && Object.keys(updatedTenant).length > 0) {
await this.client.tenant.updateSettings(updatedTenant);
this.updated += 1;
this.didUpdate(tenant);
this.didUpdate(updatedTenant);
}
}
}

export const sanitizeMigrationFlags = ({
existingFlags = {},
proposedFlags = {},
}: {
existingFlags: Tenant['flags'];
proposedFlags: Tenant['flags'];
}): Tenant['flags'] => {
/*
Tenants can only update migration flags that are already configured.
If moving configuration from one tenant to another, there may be instances
where different migration flags exist and cause an error on update. This
function removes any migration flags that aren't already present on the target
tenant. See: https://github.com/auth0/auth0-deploy-cli/issues/374
*/

const tenantMigrationFlags = [
'disable_clickjack_protection_headers',
'enable_mgmt_api_v1',
'trust_azure_adfs_email_verified_connection_property',
'include_email_in_reset_pwd_redirect',
'include_email_in_verify_email_redirect',
];

return Object.keys(proposedFlags).reduce(
(acc: Tenant['flags'], proposedKey: string): Tenant['flags'] => {
const isMigrationFlag = tenantMigrationFlags.includes(proposedKey);
if (!isMigrationFlag)
return {
...acc,
[proposedKey]: proposedFlags[proposedKey],
};

const keyCurrentlyExists = existingFlags[proposedKey] !== undefined;
if (keyCurrentlyExists)
return {
...acc,
[proposedKey]: proposedFlags[proposedKey],
};

return acc;
},
{}
);
};
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PromptsCustomText,
PromptSettings,
} from './tools/auth0/handlers/prompts';

import { Tenant } from './tools/auth0/handlers/tenant';
import { Theme } from './tools/auth0/handlers/themes';

type SharedPaginationParams = {
Expand Down Expand Up @@ -145,8 +145,8 @@ export type BaseAuth0APIClient = {
getAll: () => Promise<Asset[]>;
};
tenant: APIClientBaseFunctions & {
getSettings: () => Promise<Asset & { enabled_locales: Language[] }>;
updateSettings: (arg0: Asset) => Promise<void>;
getSettings: () => Promise<Tenant>;
updateSettings: (arg0: Partial<Tenant>) => Promise<Tenant>;
};
triggers: APIClientBaseFunctions & {
getTriggerBindings: () => Promise<Asset>;
Expand Down Expand Up @@ -230,7 +230,7 @@ export type Assets = Partial<{
roles: Asset[] | null;
rules: Asset[] | null;
rulesConfigs: Asset[] | null;
tenant: Asset | null;
tenant: Tenant | null;
triggers: Asset[] | null;
//non-resource types
exclude?: {
Expand Down
69 changes: 69 additions & 0 deletions test/commands/import.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { expect } from 'chai';
import { findUnreplacedKeywords } from '../../src/commands/import';

describe('#findUnreplacedKeywords function', () => {
it('should not throw if no unreplaced keywords', () => {
const fn = () =>
findUnreplacedKeywords({
actions: [
{
foo: 'foo',
bar: 'bar',
},
{
foo: ['foo1', 'foo2'],
bar: 'bar',
},
{
foo: 'foo',
bar: 'bar',
},
],
tenant: {
foo: 'foo',
bar: {
some: {
nested: { property: 'bar baz' },
},
},
},
//@ts-ignore because we're detecting this
databases: ' database value ', //
});

expect(fn).to.not.throw();
});
it('should throw if unreplaced keywords detected', () => {
const fn = () =>
findUnreplacedKeywords({
actions: [
{
foo: 'foo',
bar: 'bar',
},
{
foo: ['##KEYWORD1##', '##KEYWORD2##'],
bar: 'bar',
},
{
foo: 'foo',
bar: 'bar',
},
],
tenant: {
foo: 'foo',
bar: {
some: {
nested: { property: 'bar ##KEYWORD3##' },
},
},
},
//@ts-ignore because we're detecting this
databases: ' @@KEYWORD4@@ ', //
});

expect(fn).to.throw(
'Unreplaced keywords found: KEYWORD1, KEYWORD2, KEYWORD3, KEYWORD4. Either correct these values or add to AUTH0_KEYWORD_REPLACE_MAPPINGS configuration.'
);
});
});
28 changes: 28 additions & 0 deletions test/context/yaml/pages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,34 @@ describe('#YAML context pages', () => {
);
});

it('should not throw if page HTML is not defined', async () => {
// See: https://github.com/auth0/auth0-deploy-cli/issues/365
const dir = path.join(testDataDir, 'yaml', 'pagesDump');
cleanThenMkdir(dir);
const context = new Context(
{ AUTH0_INPUT_FILE: path.join(dir, 'tennat.yaml') },
mockMgmtClient()
);

context.assets.pages = [
{ html: undefined, name: 'login' }, // HTML property is not defined here
];

const dumped = await handler.dump(context);
expect(dumped).to.deep.equal({
pages: [
{
html: './pages/login.html',
name: 'login',
},
],
});

const pagesFolder = path.join(dir, 'pages');
expect(fs.readFileSync(path.join(pagesFolder, 'login.html'), 'utf8')).to.deep.equal('');
expect(fs.readdirSync(pagesFolder).length).to.equal(1);
});

it('should dump error_page with html undefined', async () => {
const dir = path.join(testDataDir, 'yaml', 'pagesDump');
cleanThenMkdir(dir);
Expand Down
Loading

0 comments on commit e4864ce

Please sign in to comment.