FIX: Escape periods in current user's username before generating `RegExp` (#13247)

FIX: Escape periods in current user’s username before generating RegExp (#13247)

If we don’t escape periods, they are interpreted as wildcards and it becomes impossible to visit profiles of other users whose usernames match. E.g., if your username was a.c and attempted to visit abc's profile, you would be incorrectly redirected to your own profile.

diff --git a/app/assets/javascripts/discourse/app/initializers/url-redirects.js b/app/assets/javascripts/discourse/app/initializers/url-redirects.js
index 78ec8c0..8496b1f 100644
--- a/app/assets/javascripts/discourse/app/initializers/url-redirects.js
+++ b/app/assets/javascripts/discourse/app/initializers/url-redirects.js
@@ -9,8 +9,9 @@ export default {
     const currentUser = container.lookup("current-user:main");
     if (currentUser) {
       const username = currentUser.get("username");
+      const escapedUsername = username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
       DiscourseURL.rewrite(
-        new RegExp(`^/u/${username}/?$`, "i"),
+        new RegExp(`^/u/${escapedUsername}/?$`, "i"),
         `/u/${username}/activity`
       );
     }
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
index ae7603b..7c4137a 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
@@ -1,6 +1,7 @@
 import {
   acceptance,
   exists,
+  query,
   queryAll,
 } from "discourse/tests/helpers/qunit-helpers";
 import { click, currentRouteName, visit } from "@ember/test-helpers";
@@ -97,3 +98,26 @@ acceptance("User Routes", function (needs) {
     );
   });
 });
+
+acceptance(
+  "User Routes - Periods in current user's username",
+  function (needs) {
+    needs.user({ username: "e.il.rout" });
+
+    test("Periods in current user's username don't act like wildcards", async function (assert) {
+      await visit("/u/eviltrout");
+      assert.equal(
+        query(".user-profile-names .username").textContent.trim(),
+        "eviltrout",
+        "eviltrout profile is shown"
+      );
+
+      await visit("/u/e.il.rout");
+      assert.equal(
+        query(".user-profile-names .username").textContent.trim(),
+        "e.il.rout",
+        "e.il.rout profile is shown"
+      );
+    });
+  }
+);
diff --git a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
index da0e300..608d916 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
@@ -3022,4 +3022,301 @@ export default {
       top_categories: [],
     },
   },
+  "/u/e.il.rout.json": {
+    user_badges: [
+      {
+        id: 5870,
+        granted_at: "2014-05-16T02:39:38.388Z",
+        badge_id: 4,
+        user_id: 19,
+        granted_by_id: -1,
+      },
+      {
+        id: 40673,
+        granted_at: "2014-03-31T14:23:18.060Z",
+        post_id: 7241,
+        post_number: 19,
+        badge_id: 23,
+        user_id: 19,
+        granted_by_id: -1,
+        topic_id: 3153,
+      },
+      {
+        id: 5868,
+        granted_at: "2014-05-16T02:39:38.380Z",
+        badge_id: 3,
+        user_id: 19,
+        granted_by_id: -1,
+      },
+    ],
+    badges: [
+      {
+        id: 4,
+        name: "Leader",
+        description: null,
+        grant_count: 7,
+        allow_title: true,
+        multiple_grant: false,
+        icon: "fa-user",
+        image: null,
+        listable: true,
+        enabled: true,
+        badge_grouping_id: 4,
+        system: true,
+        badge_type_id: 1,
+      },
+      {
+        id: 23,
+        name: "Great Share",
+        description: null,
+        grant_count: 14,
+        allow_title: false,
+        multiple_grant: true,
+        icon: "fa-certificate",
+        image: null,
+        listable: true,
+        enabled: true,
+        badge_grouping_id: 2,
+        system: true,
+        badge_type_id: 1,
+      },
+      {
+        id: 3,
+        name: "Regular",
+        description: null,
+        grant_count: 30,
+        allow_title: true,
+        multiple_grant: false,
+        icon: "fa-user",
+        image: null,
+        listable: true,
+        enabled: true,
+        badge_grouping_id: 4,
+        system: true,
+        badge_type_id: 2,
+      },
+    ],
+    badge_types: [
+      { id: 1, name: "Gold", sort_order: 9 },
+      { id: 2, name: "Silver", sort_order: 8 },
+      { id: 3, name: "Bronze", sort_order: 7 },
+    ],
+    users: [
+      {
+        id: 19,
+        username: "eviltrout",
+        uploaded_avatar_id: null,
+        avatar_template:
+          "/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+      },
+      {
+        id: -1,
+        username: "system",
+        uploaded_avatar_id: null,
+        avatar_template:
+          "/letter_avatar/system/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+      },
+    ],
+    topics: [
+      {
+        id: 3153,
+        title: "Is it better for Discourse to use JavaScript or CoffeeScript?",
+        fancy_title:
+          "Is it better for Discourse to use JavaScript or CoffeeScript?",
+        slug: "is-it-better-for-discourse-to-use-javascript-or-coffeescript",
+        posts_count: 56,
+      },
+    ],
+    user: {
+      user_option: {
+        text_size_seq: 1,
+      },
+      id: 4432,
+      username: "e.il.rout",
+      uploaded_avatar_id: null,
+      avatar_template:
+        "/letter_avatar/e.il.rout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+      name: "Robin Ward",
+      email: "robin.ward@example.com",
+      associated_accounts: [
+        {
+          name: "facebook",
+          description: "robin.ward@example.com",
+          can_revoke: true,
+        },
+      ],
+      last_posted_at: "2015-05-07T15:23:35.074Z",
+      last_seen_at: "2015-05-13T14:34:23.188Z",
+      bio_raw:
+        'Co-founder of Discourse. Previously, I created <a href="http://forumwarz.com">Forumwarz</a>. <a href="https://twitter.com/eviltrout">Follow me on Twitter</a>. I am @eviltrout.',
+      bio_cooked:
+        '<p>Co-founder of Discourse. Previously, I created <a href="http://forumwarz.com">Forumwarz</a>. <a href="https://twitter.com/eviltrout">Follow me on Twitter</a>. I am <a class="mention" href="/u/eviltrout">@eviltrout</a>.</p>',
+      created_at: "2013-02-03T15:19:22.704Z",
+      website: "http://eviltrout.com",
+      location: "Toronto",
+      can_edit: false,
+      can_edit_username: true,
+      can_edit_email: true,
+      can_edit_name: true,
+      stats: [
+        { action_type: 13, count: 342, id: null },
+        { action_type: 12, count: 109, id: null },
+        { action_type: 4, count: 27, id: null },
+        { action_type: 5, count: 1607, id: null },
+        { action_type: 6, count: 771, id: null },
+        { action_type: 1, count: 333, id: null },
+        { action_type: 2, count: 2671, id: null },
+        { action_type: 7, count: 949, id: null },
+        { action_type: 9, count: 42, id: null },
+        { action_type: 3, count: 8, id: null },
+        { action_type: 11, count: 20, id: null },
+      ],
+      can_send_private_messages: true,
+      can_send_private_message_to_user: false,
+      bio_excerpt:
+        '<p>Co-founder of Discourse. Previously, I created <a href="http://forumwarz.com">Forumwarz</a>. <a href="https://twitter.com/eviltrout">Follow me on Twitter</a>. I am <a class="mention" href="/u/eviltrout">@eviltrout</a>.</p>',
+      trust_level: 4,
+      moderator: true,
+      admin: true,
+      title: "co-founder",
+      badge_count: 23,
+      notification_count: 3244,
+      has_title_badges: true,
+      custom_fields: {},
+      user_fields: { 1: "33" },
+      pending_count: 0,
+      post_count: 1987,
+      can_be_deleted: false,
+      can_delete_all_posts: false,
+      locale: "",
+      email_digests: true,
+      email_messages_level: 0,
+      email_level: 1,
+      digest_after_minutes: 10080,
+      mailing_list_mode: false,
+      auto_track_topics_after_msecs: 60000,
+      new_topic_duration_minutes: 1440,
+      external_links_in_new_tab: false,
+      dynamic_favicon: true,
+      skip_new_user_tips: false,

[... diff too long, it was truncated ...]

GitHub sha: 3249312c

1 Like

This commit appears in #13247 which was approved by SamSaffron. It was merged by SamSaffron.