Repository: auth0/auth0-deploy-cli

Issue:
Exporting Branding Templates (universal login template) silently fails if read:custom_domains scope is not granted
### Checklist

- [x] I have looked into the [README](https://github.com/auth0/auth0-deploy-cli#readme) and have not found a suitable solution or answer.
- [x] I have looked into the [documentation](https://github.com/auth0/auth0-deploy-cli#documentation) and have not found a suitable solution or answer.
- [x] I have searched the [issues](https://github.com/auth0/auth0-deploy-cli/issues) and have not found a suitable solution or answer.
- [x] I have upgraded to the [latest version](https://github.com/auth0/auth0-deploy-cli/releases/latest) of this tool and the issue still persists.
- [x] I have searched the [Auth0 Community](https://community.auth0.com/tag/auth0-deploy-cli) forums and have not found a suitable solution or answer.
- [x] I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).

### Description

At this line of code, a request for custom domains is made:
https://github.com/auth0/auth0-deploy-cli/blob/master/src/tools/auth0/handlers/branding.ts#L45

If the user does not provide custom domain scopes, I believe this returns a 403. In any case, the custom domains don't get set and then the branding template silently does not get exported due to the conditional at this line: https://github.com/auth0/auth0-deploy-cli/blob/master/src/tools/auth0/handlers/branding.ts#L47

We found that adding the read:custom_domains scope fixed the issue.

### Expectation

The CLI should give a warning if the `read:custom_domains` scope is not set and the branding templates does not get exported as a result.

### Reproduction

Run a0deploy export with a config.json that includes `branding`, but without the `read:custom_domains` scope.
Result: No universal login template is exported.

### Deploy CLI version

8.6.2

### Node version

v20.15.0

Hints from issue comments:
@kushalshit27: Hi, @nathanKramer 
Thank you for reporting this issue! We have started working on a fix and will keep you updated on our progress. @kushalshit27: Hi, @nathanKramer 
With `--debug` flag you can get proper debug log. But we have also added warn log on #1047  

The fix is ready and will be part of our next release. We appreciate your understanding and support, Thanks for your support!
@kushalshit27: The fix for this issue is now available in version [8.7.0](https://github.com/auth0/auth0-deploy-cli/releases/tag/v8.7.0). 🚀@kushalshit27: Thank you for your patience! This issue fix is now live. Feel free to reopen it if you encounter any further problems.

Solution patch:
diff --git a/docs/resource-specific-documentation.md b/docs/resource-specific-documentation.md
index dc2a207..c5234f6 100644
--- a/docs/resource-specific-documentation.md
+++ b/docs/resource-specific-documentation.md
@@ -368,3 +368,69 @@ Contents of `reset_email_by_code.json`
   "html": "./reset_email_by_code.html"
 }
 ```
+
+## Branding
+
+This resource allows to manage branding within your Auth0 tenant. Auth0 can be customized with a look and feel that aligns with your organization's brand requirements and user expectations. `universal_login` template can be customized (make sure to add `read:custom_domains` scope to export templates).
+
+**YAML Example**
+
+Folder structure when in YAML mode.
+
+```yaml
+branding:
+  colors:
+    page_background: '#FF4F40'
+    primary: '#2A2E35'
+  favicon_url: https://example.com/favicon.png
+  font:
+    url: https://example.com/font.woff
+  logo_url: https://example.com/logo.png
+  templates:
+    - template: universal_login
+      body: ./branding_templates/universal_login.html
+```
+
+**Directory Example**
+
+Folder structure when in directory mode.
+
+```json
+{
+  "colors": {
+    "page_background": "#FF4F40",
+    "primary": "#2A2E35"
+  },
+  "favicon_url": "https://example.com/favicon.png",
+  "font": {
+    "url": "https://example.com/font.woff"
+  },
+  "logo_url": "https://example.com/logo.png"
+}
+```
+
+For `universal_login` template `templates/` will be created.
+
+- `templates/universal_login.html`
+
+```html
+<!DOCTYPE html>
+<html>
+  <head>
+    {%- auth0:head -%}
+  </head>
+  <body>
+    {%- auth0:widget -%}
+    <div>page teamplate</div>
+  </body>
+</html>
+```
+
+- `templates/universal_login.json`
+
+```json
+{
+  "template": "universal_login",
+  "body": "./universal_login.html"
+}
+```
diff --git a/src/tools/auth0/handlers/branding.ts b/src/tools/auth0/handlers/branding.ts
index 789b3b3..4a894e2 100644
--- a/src/tools/auth0/handlers/branding.ts
+++ b/src/tools/auth0/handlers/branding.ts
@@ -73,7 +73,14 @@ export default class BrandingHandler extends DefaultHandler {
       return branding;
     } catch (err) {
       log.debug(`Error calling branding API, ${err.message}, status code: ${err.statusCode}`);
-      if (err.statusCode === 403) return branding;
+
+      if (err.statusCode === 403) {
+        log.warn(
+          'Insufficient scope the read:custom_domains scope is not set. Branding templates will not be exported.'
+        );
+        return branding;
+      }
+
       if (err.statusCode === 404) return branding;
       if (err.statusCode === 501) return branding;
       throw err;



Test patch:
diff --git a/test/tools/auth0/handlers/branding.tests.js b/test/tools/auth0/handlers/branding.tests.js
index 87242d8..d09efeb 100644
--- a/test/tools/auth0/handlers/branding.tests.js
+++ b/test/tools/auth0/handlers/branding.tests.js
@@ -93,6 +93,34 @@ describe('#branding handler', () => {
       });
     });
 
+    it('should handle insufficient scope error and not export branding templates', async () => {
+      const auth0 = {
+        branding: {
+          getSettings: () => ({
+            data: {
+              logo_url: 'https://example.com/logo.png',
+            },
+          }),
+          getUniversalLoginTemplate: () => ({
+            body: html,
+          }),
+        },
+        customDomains: {
+          getAll: () => {
+            const err = new Error('Insufficient scope');
+            err.statusCode = 403;
+            return Promise.reject(err);
+          },
+        },
+      };
+
+      const handler = new branding.default({ client: auth0 });
+      const data = await handler.getType();
+      expect(data).to.deep.equal({
+        logo_url: 'https://example.com/logo.png',
+      });
+    });
+
     it('should update branding settings without templates if no templates set', (done) => {
       const auth0 = {
         branding: {