diff --git a/src/management/BrandingManager.js b/src/management/BrandingManager.js index 58c6955f1..b4cfb54da 100644 --- a/src/management/BrandingManager.js +++ b/src/management/BrandingManager.js @@ -56,6 +56,19 @@ class BrandingManager { options.tokenProvider ); this.brandingTemplates = new RetryRestClient(brandingTemplateAuth0RestClient, options.retry); + + /** + * Provides an abstraction layer for consuming the + * {@link https://auth0.com/docs/api/management/v2#!/Branding/get_branding_theme Branding theme endpoint}. + * + * @type {external:RestClient} + */ + const brandingThemesAuth0RestClient = new Auth0RestClient( + `${options.baseUrl}/branding/themes/:id`, + clientOptions, + options.tokenProvider + ); + this.brandingThemes = new RetryRestClient(brandingThemesAuth0RestClient, options.retry); } /** @@ -156,6 +169,115 @@ class BrandingManager { deleteUniversalLoginTemplate(...args) { return this.brandingTemplates.delete(...args); } + + /** + * Get the new universal login theme by id. + * + * @example + * var params = { id: THEME_ID }; + * + * management.branding.getTheme(params, function (err, theme) { + * if (err) { + * // Handle error. + * } + * + * // Theme + * console.log(theme); + * }); + * @param {object} params Theme params + * @param {object} params.id Theme identifier. + * @param {Function} [cb] Callback function. + * @returns {Promise|undefined} + */ + getTheme(...args) { + return this.brandingThemes.get(...args); + } + + /** + * Get the default new universal login theme. + * + * @example + * management.branding.getDefaultTheme(function (err, theme) { + * if (err) { + * // Handle error. + * } + * + * // Theme + * console.log(theme); + * }); + * @param {Function} [cb] Callback function. + * @returns {Promise|undefined} + */ + getDefaultTheme(...args) { + return this.brandingThemes.get(...[{ id: 'default' }].concat(args)); + } + + /** + * Create a new theme. + * + * @example + * management.branding.createTheme(data, function (err, theme) { + * if (err) { + * // Handle error. + * } + * + * // Theme created. + * console.log(theme); + * }); + * @param {object} data theme data object. + * @param {Function} [cb] Callback function. + * @returns {Promise|undefined} + */ + createTheme(...args) { + return this.brandingThemes.create(...args); + } + + /** + * Update a theme. + * + * @example + * var data = THEME_OBJ; + * var params = { id: THEME_ID }; + * + * management.branding.updateTheme(params, data, function (err, theme) { + * if (err) { + * // Handle error. + * } + * + * // Theme updated. + * console.log(theme); + * }); + * @param {object} params Theme parameters. + * @param {object} params.id Theme identifier. + * @param {object} data Updated theme data. + * @param {Function} [cb] Callback function. + * @returns {Promise|undefined} + */ + updateTheme(...args) { + return this.brandingThemes.patch(...args); + } + + /** + * Delete a theme. + * + * @example + * var params = { id: THEME_ID }; + * + * management.branding.deleteTheme(params, function (err) { + * if (err) { + * // Handle error. + * } + * + * // Theme deleted. + * }); + * @param {object} params Theme parameters. + * @param {object} params.id Theme identifier. + * @param {Function} [cb] Callback function. + * @returns {Promise|undefined} + */ + deleteTheme(...args) { + return this.brandingThemes.delete(...args); + } } module.exports = BrandingManager; diff --git a/test/data/theme.json b/test/data/theme.json new file mode 100644 index 000000000..38d23708d --- /dev/null +++ b/test/data/theme.json @@ -0,0 +1,74 @@ +{ + "borders": { + "button_border_radius": 3, + "button_border_weight": 1, + "buttons_style": "rounded", + "input_border_radius": 3, + "input_border_weight": 1, + "inputs_style": "rounded", + "show_widget_shadow": true, + "widget_border_weight": 0, + "widget_corner_radius": 5 + }, + "colors": { + "body_text": "#1e212a", + "error": "#d03c38", + "header": "#1e212a", + "icons": "#65676e", + "input_background": "#ffffff", + "input_border": "#c9cace", + "input_filled_text": "#000000", + "input_labels_placeholders": "#65676e", + "links_focused_components": "#635dff", + "primary_button": "#de7878", + "primary_button_label": "#ffffff", + "secondary_button_border": "#c9cace", + "secondary_button_label": "#1e212a", + "success": "#13a688", + "widget_background": "#ffffff", + "widget_border": "#c9cace" + }, + "fonts": { + "body_text": { + "bold": false, + "size": 87.5 + }, + "buttons_text": { + "bold": false, + "size": 100 + }, + "font_url": "", + "input_labels": { + "bold": false, + "size": 100 + }, + "links": { + "bold": true, + "size": 87.5 + }, + "links_style": "normal", + "reference_text_size": 16, + "subtitle": { + "bold": false, + "size": 87.5 + }, + "title": { + "bold": false, + "size": 150 + } + }, + "page_background": { + "background_color": "#000000", + "background_image_url": "", + "page_layout": "center" + }, + "widget": { + "header_text_alignment": "center", + "logo_height": 52, + "logo_position": "center", + "logo_url": "", + "social_buttons_layout": "bottom" + }, + "themeId": "themeid1", + "displayName": "default theme" +} diff --git a/test/management/branding.tests.js b/test/management/branding.tests.js index 398ac8d25..813d84d61 100644 --- a/test/management/branding.tests.js +++ b/test/management/branding.tests.js @@ -1,5 +1,8 @@ const { expect } = require('chai'); +const fs = require('fs'); +const path = require('path'); const nock = require('nock'); +const util = require('util'); const API_URL = 'https://tenant.auth0.com'; @@ -7,16 +10,27 @@ const BrandingManager = require(`../../src/management/BrandingManager`); const { ArgumentError } = require('rest-facade'); describe('BrandingManager', () => { + let branding, token; + before(function () { this.token = 'TOKEN'; this.branding = new BrandingManager({ headers: { authorization: `Bearer ${this.token}` }, baseUrl: API_URL, }); + ({ branding, token } = this); }); describe('instance', () => { - const methods = ['getSettings', 'updateSettings']; + const methods = [ + 'getSettings', + 'updateSettings', + 'getTheme', + 'getDefaultTheme', + 'createTheme', + 'updateTheme', + 'deleteTheme', + ]; methods.forEach((method) => { it(`should have a ${method} method`, function () { @@ -438,4 +452,298 @@ describe('BrandingManager', () => { }); }); }); + + describe('#getTheme', () => { + beforeEach(() => {}); + + it('should accept a callback', (done) => { + nock(API_URL).get('/branding/themes/themeid1').reply(200); + + branding.getTheme({ id: 'themeid1' }, () => { + done(); + }); + }); + + it('should return a promise if no callback is given', async () => { + nock(API_URL).get('/branding/themes/themeid1').reply(200); + + const promise = branding.getTheme({ id: 'themeid1' }); + expect(promise.then).to.exist; + expect(promise.catch).to.exist; + await promise; + }); + + it('should pass any errors to the promise catch handler', async () => { + nock(API_URL).get('/branding/themes/themeid1').reply(404); + + try { + await branding.getTheme({ id: 'themeid1' }); + } catch (err) { + expect(err.statusCode).to.eq(404); + expect(err).to.exist; + } + }); + + it('should pass the body of the response to the "then" handler', async () => { + const data = JSON.parse( + await util.promisify(fs.readFile)(path.join(__dirname, '../data/theme.json')) + ); + nock(API_URL).get('/branding/themes/themeid1').reply(200, data); + + const theme = await branding.getTheme({ id: 'themeid1' }); + expect(theme.themeId).to.equal(data.themeId); + }); + + it('should perform a GET request to /api/v2/branding/themes/:theme_id', async () => { + const request = nock(API_URL).get('/branding/themes/themeid1').reply(200); + + await branding.getTheme({ id: 'themeid1' }); + expect(request.isDone()).to.be.true; + }); + + it('should include the token in the Authorization header', async () => { + const request = nock(API_URL) + .get('/branding/themes/themeid1') + .matchHeader('Authorization', `Bearer ${token}`) + .reply(200, { id: 1 }); + + await branding.getTheme({ id: 'themeid1' }); + expect(request.isDone()).to.be.true; + }); + }); + + describe('#getDefaultTheme', () => { + beforeEach(() => {}); + + it('should accept a callback', (done) => { + nock(API_URL).get('/branding/themes/default').reply(200); + + branding.getDefaultTheme(() => { + done(); + }); + }); + + it('should return a promise if no callback is given', async () => { + nock(API_URL).get('/branding/themes/default').reply(200); + + const promise = branding.getDefaultTheme(); + expect(promise.then).to.exist; + expect(promise.catch).to.exist; + await promise; + }); + + it('should pass any errors to the promise catch handler', async () => { + nock(API_URL).get('/branding/themes/default').reply(404); + + try { + await branding.getDefaultTheme(); + } catch (err) { + expect(err.statusCode).to.eq(404); + expect(err).to.exist; + } + }); + + it('should pass the body of the response to the "then" handler', async () => { + const data = JSON.parse( + await util.promisify(fs.readFile)(path.join(__dirname, '../data/theme.json')) + ); + nock(API_URL).get('/branding/themes/default').reply(200, data); + + const theme = await branding.getDefaultTheme(); + expect(theme.themeId).to.equal(data.themeId); + }); + + it('should perform a GET request to /api/v2/branding/themes/default', async () => { + const request = nock(API_URL).get('/branding/themes/default').reply(200); + + await branding.getDefaultTheme(); + expect(request.isDone()).to.be.true; + }); + + it('should include the token in the Authorization header', async () => { + const request = nock(API_URL) + .get('/branding/themes/default') + .matchHeader('Authorization', `Bearer ${token}`) + .reply(200, { id: 1 }); + + await branding.getDefaultTheme(); + expect(request.isDone()).to.be.true; + }); + }); + + describe('#createTheme', () => { + let data; + beforeEach(async () => { + data = JSON.parse( + await util.promisify(fs.readFile)(path.join(__dirname, '../data/theme.json')) + ); + }); + + it('should accept a callback', (done) => { + nock(API_URL).post(`/branding/themes`, data).reply(201); + + branding.createTheme(data, () => { + done(); + }); + }); + + it('should return a promise if no callback is given', async () => { + nock(API_URL).post(`/branding/themes`, data).reply(201); + + const promise = branding.createTheme(data); + expect(promise.then).to.exist; + expect(promise.catch).to.exist; + await promise; + }); + + it('should pass any errors to the promise catch handler', async () => { + nock(API_URL).post(`/branding/themes`, data).reply(409); + + try { + await branding.createTheme(data); + } catch (err) { + expect(err.statusCode).to.eq(409); + expect(err).to.exist; + } + }); + + it('should pass the body of the response to the "then" handler', async () => { + nock(API_URL).post(`/branding/themes`, data).reply(201, data); + + const theme = await branding.createTheme(data); + expect(theme.themeId).to.equal(data.themeId); + }); + + it('should perform a POST request to /branding/themes', async () => { + const request = nock(API_URL).post(`/branding/themes`, data).reply(201, data); + + await branding.createTheme(data); + expect(request.isDone()).to.be.true; + }); + + it('should include the token in the Authorization header', async () => { + const request = nock(API_URL) + .post('/branding/themes', data) + .matchHeader('Authorization', `Bearer ${token}`) + .reply(201, data); + + await branding.createTheme(data); + expect(request.isDone()).to.be.true; + }); + }); + + describe('#updateTheme', () => { + let data, themeId, params; + beforeEach(async () => { + ({ themeId, ...data } = JSON.parse( + await util.promisify(fs.readFile)(path.join(__dirname, '../data/theme.json')) + )); + params = { id: themeId }; + }); + + it('should accept a callback', (done) => { + nock(API_URL).patch(`/branding/themes/${themeId}`, data).reply(200); + + branding.updateTheme(params, data, () => { + done(); + }); + }); + + it('should return a promise if no callback is given', async () => { + nock(API_URL).patch(`/branding/themes/${themeId}`, data).reply(200); + + const promise = branding.updateTheme(params, data); + expect(promise.then).to.exist; + expect(promise.catch).to.exist; + await promise; + }); + + it('should pass any errors to the promise catch handler', async () => { + nock(API_URL).patch(`/branding/themes/${themeId}`, data).reply(404); + + try { + await branding.updateTheme(params, data); + } catch (err) { + expect(err.statusCode).to.eq(404); + expect(err).to.exist; + } + }); + + it('should pass the body of the response to the "then" handler', async () => { + nock(API_URL).patch(`/branding/themes/${themeId}`, data).reply(200, data); + + const theme = await branding.updateTheme(params, data); + expect(theme.themeId).to.equal(data.themeId); + }); + + it('should perform a PATCH request to /api/v2/branding/themes/:theme_id', async () => { + const request = nock(API_URL).patch(`/branding/themes/${themeId}`, data).reply(200, data); + + await branding.updateTheme(params, data); + expect(request.isDone()).to.be.true; + }); + + it('should include the token in the Authorization header', async () => { + const request = nock(API_URL) + .patch(`/branding/themes/${themeId}`, data) + .matchHeader('Authorization', `Bearer ${token}`) + .reply(200, data); + + await branding.updateTheme(params, data); + expect(request.isDone()).to.be.true; + }); + }); + + describe('#deleteTheme', () => { + let themeId, params; + beforeEach(async () => { + themeId = 'themeid1'; + params = { id: themeId }; + }); + + it('should accept a callback', (done) => { + nock(API_URL).delete(`/branding/themes/${themeId}`).reply(204); + + branding.deleteTheme(params, () => { + done(); + }); + }); + + it('should return a promise if no callback is given', async () => { + nock(API_URL).delete(`/branding/themes/${themeId}`).reply(204); + + const promise = branding.deleteTheme(params); + expect(promise.then).to.exist; + expect(promise.catch).to.exist; + await promise; + }); + + it('should pass any errors to the promise catch handler', async () => { + nock(API_URL).delete(`/branding/themes/${themeId}`).reply(404); + + try { + await branding.deleteTheme(params); + } catch (err) { + expect(err.statusCode).to.eq(404); + expect(err).to.exist; + } + }); + + it('should perform a PATCH request to /api/v2/branding/themes/:theme_id', async () => { + const request = nock(API_URL).delete(`/branding/themes/${themeId}`).reply(204); + + await branding.deleteTheme(params); + expect(request.isDone()).to.be.true; + }); + + it('should include the token in the Authorization header', async () => { + const request = nock(API_URL) + .delete(`/branding/themes/${themeId}`) + .matchHeader('Authorization', `Bearer ${token}`) + .reply(204); + + await branding.deleteTheme(params); + expect(request.isDone()).to.be.true; + }); + }); });