Repository: firebase/firebase-tools (ts)

Issue:
`addAuthDomains` does not add multiple domains
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

<!--
Thank you for contributing to the Firebase community!

Think you found a bug?
=======================
Yeah, we're definitely not perfect! Please use this template and include a minimal repro when opening the issue. If you know how to solve the issue, please create a Pull Request, and we'd be happy to review it!

Have a feature request?
========================
Great, we love hearing how we can improve our products! However, do not use this template to submit a feature request. Please submit your feature requests to: https://firebase.google.com/support/contact/bugs-features/

Have a usage question?
=======================
We get lots of those and we love helping you, but GitHub is not the best place for them and they will be closed. Please take a look at the guide first: https://firebase.google.com/docs/cli/

If the official documentation doesn't help, try asking through our official support channel: https://firebase.google.com/support/

Additional locations to check for solutions or assistance from the community:
- Stack Overflow: https://stackoverflow.com/
- Firebase Slack Community: https://firebase.community/

*Please avoid duplicate posting across multiple channels!*
-->

### [REQUIRED] Environment info

<!-- What version of the Firebase CLI (`firebase-tools`) are you using? Note that your issue may already be fixed in the latest versions. The latest version can be found at https://github.com/firebase/firebase-tools/releases -->

<!-- Output of `firebase --version` -->

**firebase-tools:** 12.5.3

<!-- e.g. macOS, Windows, Ubuntu -->

**Platform:** Ubuntu

### [REQUIRED] Test case

Hi,
we have multiple sites defined in our `.firebaserc`, ie:

```json
{
  "projects": {
    "default": "proj"
  },
  "targets": {
    "proj": {
      "hosting": {
        "site1": [
          "site1"
        ],
        "site2": [
          "site2"
        ]
      }
    }
  }
}
```

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->

### [REQUIRED] Steps to reproduce

If I deploy to a channel:

```bash
$ npx firebase-tools@12.5.3 hosting:channel:deploy zzz --expires 7d --project proj --debug
...
[2023-09-12T15:58:19.040Z] <<< [apiv2][body] PATCH https://identitytoolkit.googleapis.com/admin/v2/projects/proj/config
{
    "name": "projects/123/config",
    "signIn": {
        "email": {
            "enabled": true
        },
        "hashConfig": {
            "algorithm": "SCRYPT",
            "signerKey": "key",
            "saltSeparator": "Bw==",
            "rounds": 8,
            "memoryCost": 14
        }
    },
    "notification": {
        "sendEmail": {
            "method": "DEFAULT",
            "callbackUri": "https://proj.firebaseapp.com/__/auth/action",
            "dnsInfo": {
                "customDomainState": "NOT_STARTED",
                "domainVerificationRequestTime": "1970-01-01T00:00:00Z"
            }
        },
        "sendSms": {
            "smsTemplate": {
                "content": "%LOGIN_CODE% is your verification code for %APP_NAME%."
            }
        },
        "defaultLocale": "en"
    },
    "quota": {},
    "monitoring": {
        "requestLogging": {}
    },
    "multiTenant": {},
    "authorizedDomains": [
        "localhost",
        ...
        "site1-random--zzz-p74ozsma.web.app",
        "site2-random--zzz-yehbjsw1.web.app"
    ],
    "subtype": "FIREBASE_AUTH",
    "client": {
        "apiKey": "key",
        "permissions": {},
        "firebaseSubdomain": "proj"
    },
    "mfa": {
        "state": "DISABLED"
    },
    "blockingFunctions": {},
    "smsRegionConfig": {}
}
[2023-09-12T15:48:54.800Z] [hosting] added auth domain for urls ["https://site1-random--zzz-yehbjsw1.web.app","https://site2-random--zzz-yehbjsw1.web.app"]
```

the logs says that 2 domains have been added but the firebase console, "Authorized domains", shows only the first site.

Instead if I deploy each site separately:

```bash
$ npx firebase-tools@12.5.3 hosting:channel:deploy zzz --only site1 --expires 7d --project proj --debug
...
$ npx firebase-tools@12.5.3 hosting:channel:deploy zzz --only site2 --expires 7d --project proj --debug
...
```

now both sites shows in the firebase console.

Now if I rerun:

```bash
$ npx firebase-tools@12.5.3 hosting:channel:deploy zzz --expires 7d --project proj --debug
```

the second site disappear from the "Authorized domains" list.

<!-- Provide the steps needed to reproduce the issue with the above test case. -->

### [REQUIRED] Expected behavior

<!-- What is the expected behavior? -->
Both sites are in the "Authorized domains" list 

### [REQUIRED] Actual behavior

Only 1 site is in "Authorize domains" list

<!-- Run the command with --debug flag, and include the logs below. -->
Firebase tools doesn't deploy NextJs web framework for NextJs >=13.5.0
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

<!--
Thank you for contributing to the Firebase community!

Think you found a bug?
=======================
Yeah, we're definitely not perfect! Please use this template and include a minimal repro when opening the issue. If you know how to solve the issue, please create a Pull Request, and we'd be happy to review it!

Have a feature request?
========================
Great, we love hearing how we can improve our products! However, do not use this template to submit a feature request. Please submit your feature requests to: https://firebase.google.com/support/contact/bugs-features/

Have a usage question?
=======================
We get lots of those and we love helping you, but GitHub is not the best place for them and they will be closed. Please take a look at the guide first: https://firebase.google.com/docs/cli/

If the official documentation doesn't help, try asking through our official support channel: https://firebase.google.com/support/

Additional locations to check for solutions or assistance from the community:
- Stack Overflow: https://stackoverflow.com/
- Firebase Slack Community: https://firebase.community/

*Please avoid duplicate posting across multiple channels!*
-->

### [REQUIRED] Environment info

<!-- What version of the Firebase CLI (`firebase-tools`) are you using? Note that your issue may already be fixed in the latest versions. The latest version can be found at https://github.com/firebase/firebase-tools/releases -->

<!-- Output of `firebase --version` -->

**firebase-tools:** 12.5.4

<!-- e.g. macOS, Windows, Ubuntu -->

**Platform:** Ubuntu

### [REQUIRED] Test case

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->
Deploying Next.js >= v13.5.0 to firebase doesn't complete due to error. All permission and account setup is correct.

### [REQUIRED] Steps to reproduce
<!-- Provide the steps needed to reproduce the issue with the above test case. -->
- Setup firebase for nextjs deployment. 
- Deploy project using `firebase hosting:channel:deploy new-awesome-feature --expires 7d`

### [REQUIRED] Expected behavior
<!-- What is the expected behavior? -->
Deployment complete successfully

### [REQUIRED] Actual behavior
<!-- Run the command with --debug flag, and include the logs below. -->
```
TypeError: Cannot destructure property 'customConfig' of '(intermediate value)(intermediate value)(intermediate value)' as it is null.
.
.
.  
The process '/usr/local/bin/npx' failed with exit code 2
Error: The process '/usr/local/bin/npx' failed with exit code 2
```

Pull request:
corrects regex for domain matching authorized domains
<!--

Thank you for contributing to the Firebase community! Please fill out the form below.

Run the linter and test suite
==============================
Run `npm test` to make sure your changes compile properly and the tests all pass on your local machine. We've hooked up this repo with continuous integration to double check those things for you.

-->

### Description

<!-- Are you fixing a bug? Implementing a new feature? Make sure we have the context around your change. Link to other relevant issues or pull requests. -->

Fixes #6356

There's a case where if two sites are deployed such as `my-site` and `foo-my-site` that the cleanup mechanism around auth domains will try to update the list for `my-site` but see a channel domain like `foo-my-site--channel-random8`, for the other site, and say "hey, that's not a channel for `my-site` any more, delete it!".

By making the check look for the _start_ of the domain to match the site name (and still end with the `--`, the delineator for a Hosting channel name, we eliminate that issue.

### Scenarios Tested

<!-- Write a list of all the user journeys and edge cases you've tested. Instructions for manual testing can be found at https://github.com/firebase/firebase-tools/blob/master/.github/CONTRIBUTING.md#development-setup -->

I set up a pair of sites like above and tested on `master` and my branch, and it works correctly now!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f117bab5..976185315 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2 +1,3 @@
 - Enable [preferRest](https://firebase.google.com/docs/reference/admin/node/firebase-admin.firestore.firestoresettings.md#firestoresettingspreferrest) option by default for Firestore functions. (#6147)
 - Fixed a bug where re-deploying 2nd Gen Firestore function failed after updating secrets. (#6456)
+- Fixed a bug where similarly-named Hosting channels would cause issues when updating authorized domains. (#6356)
diff --git a/src/hosting/api.ts b/src/hosting/api.ts
index 552a4ad19..4b921ff59 100644
--- a/src/hosting/api.ts
+++ b/src/hosting/api.ts
@@ -633,8 +633,8 @@ export async function getCleanDomains(project: string, site: string): Promise<st
       return acc;
     }, {});
 
-  // match any string that has ${site}--*
-  const siteMatch = new RegExp(`${site}--`, "i");
+  // match any string that starts with ${site}--*
+  const siteMatch = new RegExp(`^${site}--`, "i");
   // match any string that ends in firebaseapp.com
   const firebaseAppMatch = new RegExp(/firebaseapp.com$/);
   const domains = await getAuthDomains(project);


Test patch:
diff --git a/src/test/hosting/api.spec.ts b/src/test/hosting/api.spec.ts
index 706602e19..8e0b34329 100644
--- a/src/test/hosting/api.spec.ts
+++ b/src/test/hosting/api.spec.ts
@@ -711,6 +711,41 @@ describe("hosting", () => {
       expect(res).to.deep.equal(EXPECTED_DOMAINS_RESPONSE);
       expect(nock.isDone()).to.be.true;
     });
+
+    it("should not remove sites that are similarly named", async () => {
+      // mock listChannels response
+      nock(hostingApiOrigin)
+        .get(`/v1beta1/projects/${PROJECT_ID}/sites/${SITE}/channels`)
+        .query(() => true)
+        .reply(200, {
+          channels: [
+            { url: "https://my-site--ch1-4iyrl1uo.web.app" },
+            { url: "https://my-site--ch2-ygd8582v.web.app" },
+          ],
+        });
+      // mock getAuthDomains response
+      nock(identityOrigin)
+        .get(`/admin/v2/projects/${PROJECT_ID}/config`)
+        .reply(200, {
+          authorizedDomains: [
+            "localhost",
+            "randomurl.com",
+            "my-site--ch1-4iyrl1uo.web.app",
+            "my-site--expiredchannel-difhyc76.web.app",
+            "backendof-my-site--some-abcd1234.web.app",
+          ],
+        });
+
+      const res = await hostingApi.getCleanDomains(PROJECT_ID, SITE);
+
+      expect(res).to.deep.equal([
+        "localhost",
+        "randomurl.com",
+        "my-site--ch1-4iyrl1uo.web.app",
+        "backendof-my-site--some-abcd1234.web.app",
+      ]);
+      expect(nock.isDone()).to.be.true;
+    });
   });
 
   describe("getSiteDomains", () => {