consider path when determining if an IA profile user can view a topic
authorJustin Wind <justin.wind+git@gmail.com>
Thu, 3 Nov 2022 20:09:32 +0000 (13:09 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Thu, 3 Nov 2022 20:11:39 +0000 (13:11 -0700)
CHANGELOG.md
src/manager.js
test/src/manager.js

index 810434feefb5f562ec07508bde5eebd9f0b29412..cdf9b9a7eda34664148234ee88192c6320a4d348 100644 (file)
@@ -6,6 +6,7 @@ Releases and notable changes to this project are documented here.
 
 ### Fixed
 
+- Path prefix is now considered when determining whether an IndieAuth profile user can view a topic.
 - Fixed non-default topic lease durations issues with postgres.
 - Dependency updates.
 
index 9d4a282c2aac17864d3e330ba4e655cd424493fc..b6d2ccb6322fa2162662cc925e0859b4927d42e2 100644 (file)
@@ -606,6 +606,21 @@ class Manager {
     this.logger.info(_scope, 'finished', { ctx });
   }
 
+
+  /**
+   * Determine if a profile url matches enough of a topic url to describe control over it.
+   * Topic must match hostname and start with the profile's path.
+   * @param {URL} profileUrlObj
+   * @param {URL} topicUrlObj
+   * @returns {Boolean}
+   */
+  static _profileControlsTopic(profileUrlObj, topicUrlObj) {
+    const hostnameMatches = profileUrlObj.hostname === topicUrlObj.hostname;
+    const pathIsPrefix = topicUrlObj.pathname.startsWith(profileUrlObj.pathname);
+    return hostnameMatches && pathIsPrefix;
+  }
+
+
   /**
    * GET request for authorized /admin information.
    * @param {http.ServerResponse} res
@@ -625,7 +640,7 @@ class Manager {
       const profileUrlObj = new URL(ctx.session.authenticatedProfile);
       ctx.topics = ctx.topics.filter((topic) => {
         const topicUrlObj = new URL(topic.url);
-        return (topicUrlObj.hostname === profileUrlObj.hostname);
+        return Manager._profileControlsTopic(profileUrlObj, topicUrlObj);
       });
     }
 
@@ -659,7 +674,7 @@ class Manager {
     if (ctx.session && ctx.session.authenticatedProfile) {
       const profileUrlObj = new URL(ctx.session.authenticatedProfile);
       const topicUrlObj = new URL(ctx.topic.url);
-      if (topicUrlObj.hostname !== profileUrlObj.hostname) {
+      if (!Manager._profileControlsTopic(profileUrlObj, topicUrlObj)) {
         ctx.topic = null;
         ctx.subscriptions = [];
       }
index cbd83243d3faecabdf8d77667151a230ac180f88..9d8c380ff3b300b25d2841de4dffea19b6b05cf5 100644 (file)
@@ -207,7 +207,7 @@ describe('Manager', function () {
       manager.db.topicGetById.resolves({
         id: '56c557ce-e667-11eb-bd80-0025905f714a',
         created: new Date(),
-        url: 'https://example.com/',
+        url: 'https://example.com/topic',
         leaseSecondsPreferred: 123,
         leaseSecondsMin: 12,
         leaseSecondsMax: 123456789,
@@ -254,7 +254,7 @@ describe('Manager', function () {
     });
     it('covers matching profile', async function () {
       ctx.session = {
-        authenticatedProfile: 'https://example.com/profile',
+        authenticatedProfile: 'https://example.com/',
       };
       await manager.getTopicDetails(res, ctx);
       assert(ctx.topic);
@@ -367,6 +367,34 @@ describe('Manager', function () {
     });
   }); // postRoot
 
+  describe('_profileControlsTopic', function () {
+    let profileUrlObj, topicUrlObj;
+    it('allows exact match', function () {
+      profileUrlObj = new URL('https://profile.example.com/');
+      topicUrlObj = new URL('https://profile.example.com/');
+      const result = Manager._profileControlsTopic(profileUrlObj, topicUrlObj);
+      assert.strictEqual(result, true);
+    });
+    it('allows descendent-path match', function () {
+      profileUrlObj = new URL('https://profile.example.com/');
+      topicUrlObj = new URL('https://profile.example.com/feed/atom');
+      const result = Manager._profileControlsTopic(profileUrlObj, topicUrlObj);
+      assert.strictEqual(result, true);
+    });
+    it('disallows non-descendent-path', function () {
+      profileUrlObj = new URL('https://profile.example.com/itsame');
+      topicUrlObj = new URL('https://profile.example.com/');
+      const result = Manager._profileControlsTopic(profileUrlObj, topicUrlObj);
+      assert.strictEqual(result, false);
+    });
+    it('disallows non-matched host', function () {
+      profileUrlObj = new URL('https://profile.example.com/itsame');
+      topicUrlObj = new URL('https://elsewhere.example.com/itsame/feed');
+      const result = Manager._profileControlsTopic(profileUrlObj, topicUrlObj);
+      assert.strictEqual(result, false);
+    });
+  }); // _profileControlsTopic
+
   describe('_getRootData', function () {
     it('extracts expected values', function () {
       req.getHeader.returns('user@example.com');