Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add permission checks for delegates and authors
Sebastian Martinez committed 2 years ago
commit eeba82017d151aea4a12debdf7192b5a99586950
parent ffe7bbba17948d1df85cd88ac376d5578e6f194d
5 files changed +110 -29
modified src/components/Thread.svelte
@@ -22,9 +22,9 @@
<script lang="ts" strictEvents>
  import type { Embed } from "@app/lib/file";

-
  import { tick } from "svelte";
-
  import { partial } from "lodash";
  import * as utils from "@app/lib/utils";
+
  import { partial } from "lodash";
+
  import { tick } from "svelte";

  import CommentComponent from "@app/components/Comment.svelte";
  import CommentToggleInput from "@app/components/CommentToggleInput.svelte";
@@ -36,6 +36,7 @@
  };
  export let rawPath: string;
  export let enableAttachments: boolean = false;
+
  export let canEditComment: (author: string) => true | undefined;
  export let editComment:
    | ((commentId: string, body: string, embeds: Embed[]) => Promise<void>)
    | undefined;
@@ -96,7 +97,9 @@
      timestamp={root.timestamp}
      disableEdit={root.embeds.length > 0}
      body={root.body}
-
      editComment={editComment && partial(editComment, root.id)}
+
      editComment={editComment &&
+
        canEditComment(root.author.id) &&
+
        partial(editComment, root.id)}
      handleReaction={handleReaction && partial(handleReaction, root.id)}>
      <IconSmall name="chat" slot="icon" />
    </CommentComponent>
@@ -116,7 +119,9 @@
          timestamp={reply.timestamp}
          disableEdit={reply.embeds.length > 0}
          body={reply.body}
-
          editComment={editComment && partial(editComment, reply.id)}
+
          editComment={editComment &&
+
            canEditComment(reply.author.id) &&
+
            partial(editComment, reply.id)}
          handleReaction={handleReaction &&
            partial(handleReaction, reply.id)} />
      {/each}
added src/lib/roles.ts
@@ -0,0 +1,36 @@
+
import { parseNodeId } from "@app/lib/utils";
+

+
export function isDelegate(
+
  publicKey: string | undefined,
+
  delegates: string[],
+
): true | undefined {
+
  if (!publicKey) {
+
    return undefined;
+
  }
+
  return (
+
    delegates.some(delegate => parseNodeId(delegate)?.pubkey === publicKey) ||
+
    undefined
+
  );
+
}
+

+
function matchAuthor(
+
  publicKey: string | undefined,
+
  nid: string,
+
): true | undefined {
+
  // Normalize the passed in NID
+
  const parsedNid = parseNodeId(nid);
+
  return (
+
    (publicKey && parsedNid && parsedNid.pubkey === publicKey) || undefined
+
  );
+
}
+

+
// All restricted actions are a combination of either:
+
// - the user is a delegate
+
// - the user is an author of the comment, issue, patch, etc.
+
export function isDelegateOrAuthor(
+
  publicKey: string | undefined,
+
  delegates: string[],
+
  author: string,
+
) {
+
  return isDelegate(publicKey, delegates) || matchAuthor(publicKey, author);
+
}
modified src/views/projects/Cob/Revision.svelte
@@ -45,6 +45,7 @@
  export let previousRevId: string | undefined = undefined;
  export let previousRevOid: string | undefined = undefined;
  export let first: boolean;
+
  export let canEditComment: (author: string) => true | undefined;
  export let editComment:
    | ((commentId: string, body: string, embeds: Embed[]) => Promise<void>)
    | undefined;
@@ -431,6 +432,7 @@
            enableAttachments
            rawPath={utils.getRawBasePath(projectId, baseUrl, projectHead)}
            thread={element.inner}
+
            {canEditComment}
            {editComment}
            {createReply}
            {handleReaction} />
modified src/views/projects/Issue.svelte
@@ -6,6 +6,7 @@
  import { isEqual, uniqBy, partial } from "lodash";

  import * as modal from "@app/lib/modal";
+
  import * as role from "@app/lib/roles";
  import * as router from "@app/lib/router";
  import * as utils from "@app/lib/utils";
  import { HttpdClient } from "@httpd-client";
@@ -442,7 +443,13 @@
      <CobHeader
        id={issue.id}
        title={issue.title}
-
        editTitle={session && partial(editTitle, session.id)}
+
        editTitle={session &&
+
          role.isDelegateOrAuthor(
+
            session.publicKey,
+
            project.delegates,
+
            issue.author.id,
+
          ) &&
+
          partial(editTitle, session.id)}
        on:refresh={refreshIssue}>
        <svelte:fragment slot="icon">
          <div
@@ -465,7 +472,7 @@
          {/if}
        </svelte:fragment>
        <div slot="description">
-
          {#if session && descriptionState !== "read"}
+
          {#if descriptionState !== "read"}
            <ExtendedTextarea
              enableAttachments
              body={issue.discussion[0].body}
@@ -492,7 +499,7 @@
                  baseUrl,
                  project.head,
                )} />
-
              {#if session}
+
              {#if role.isDelegateOrAuthor(session?.publicKey, project.delegates, issue.author.id)}
                <IconButton
                  title="edit description"
                  on:click={() => (descriptionState = "edit")}>
@@ -537,6 +544,11 @@
              enableAttachments
              {thread}
              {rawPath}
+
              canEditComment={partial(
+
                role.isDelegateOrAuthor,
+
                session?.publicKey,
+
                project.delegates,
+
              )}
              editComment={session && partial(editComment, session.id)}
              createReply={session && partial(createReply, session.id)}
              handleReaction={session && partial(handleReaction, session)} />
@@ -547,19 +559,23 @@
        <CommentToggleInput
          placeholder="Leave your comment"
          enableAttachments
-
          submit={session && partial(createComment, session.id)} />
+
          submit={partial(createComment, session.id)} />
        <div style:display="flex">
-
          <CobStateButton
-
            items={items.filter(([, state]) => !isEqual(state, issue.state))}
-
            {selectedItem}
-
            state={issue.state}
-
            save={session && partial(saveStatus, session.id)} />
+
          {#if role.isDelegateOrAuthor(session.publicKey, project.delegates, issue.author.id)}
+
            <CobStateButton
+
              items={items.filter(([, state]) => !isEqual(state, issue.state))}
+
              {selectedItem}
+
              state={issue.state}
+
              save={partial(saveStatus, session.id)} />
+
          {/if}
        </div>
      {/if}
    </div>
    <div class="metadata">
      <AssigneeInput
-
        locallyAuthenticated={Boolean(session)}
+
        locallyAuthenticated={Boolean(
+
          role.isDelegate(session?.publicKey, project.delegates),
+
        )}
        assignees={issue.assignees}
        submitInProgress={assigneeState === "submit"}
        on:save={async ({ detail: newAssignees }) => {
@@ -574,7 +590,9 @@
          await refreshIssue();
        }} />
      <LabelInput
-
        locallyAuthenticated={Boolean(session)}
+
        locallyAuthenticated={Boolean(
+
          role.isDelegate(session?.publicKey, project.delegates),
+
        )}
        labels={issue.labels}
        submitInProgress={labelState === "submit"}
        on:save={async ({ detail: newLabels }) => {
modified src/views/projects/Patch.svelte
@@ -42,6 +42,7 @@
  import type { ComponentProps } from "svelte";

  import * as modal from "@app/lib/modal";
+
  import * as role from "@app/lib/roles";
  import * as router from "@app/lib/router";
  import * as utils from "@app/lib/utils";
  import { HttpdClient } from "@httpd-client";
@@ -650,7 +651,13 @@
      <CobHeader
        id={patch.id}
        title={patch.title}
-
        editTitle={session && partial(editTitle, session.id)}
+
        editTitle={session &&
+
          role.isDelegateOrAuthor(
+
            session.publicKey,
+
            project.delegates,
+
            patch.author.id,
+
          ) &&
+
          partial(editTitle, session.id)}
        on:refresh={refreshPatch}>
        <svelte:fragment slot="icon">
          <div
@@ -697,7 +704,7 @@
            {:else}
              <span class="txt-missing">No description available</span>
            {/if}
-
            {#if session && descriptionState === "read"}
+
            {#if role.isDelegateOrAuthor(session?.publicKey, project.delegates, patch.author.id) && descriptionState === "read"}
              <div class="edit-buttons">
                <IconButton
                  title="edit description"
@@ -755,12 +762,12 @@
        </Radio>

        <div style:margin-left="auto">
-
          {#if session && view.name === "activity"}
+
          {#if session && role.isDelegateOrAuthor(session.publicKey, project.delegates, patch.author.id) && view.name === "activity"}
            <CobStateButton
              items={items.filter(([, state]) => !isEqual(state, patch.state))}
              {selectedItem}
              state={patch.state}
-
              save={session && partial(saveStatus, session.id)} />
+
              save={partial(saveStatus, session.id)} />
          {/if}
          {#if view.name === "commits" || view.name === "changes"}
            <div style="margin-left: auto;">
@@ -846,6 +853,11 @@
            projectHead={project.head}
            {...revision}
            first={index === 0}
+
            canEditComment={partial(
+
              role.isDelegateOrAuthor,
+
              session?.publicKey,
+
              project.delegates,
+
            )}
            editComment={session &&
              partial(editComment, session.id, revision.revisionId)}
            handleReaction={session &&
@@ -863,17 +875,22 @@
                <CommentToggleInput
                  enableAttachments
                  placeholder="Leave your comment"
-
                  submit={session &&
-
                    partial(createComment, session.id, revision.revisionId)} />
+
                  submit={partial(
+
                    createComment,
+
                    session.id,
+
                    revision.revisionId,
+
                  )} />
                <div class="connector" />
                <div style="display: flex;">
-
                  <CobStateButton
-
                    items={items.filter(
-
                      ([, state]) => !isEqual(state, patch.state),
-
                    )}
-
                    {selectedItem}
-
                    state={patch.state}
-
                    save={session && partial(saveStatus, session.id)} />
+
                  {#if role.isDelegateOrAuthor(session.publicKey, project.delegates, patch.author.id)}
+
                    <CobStateButton
+
                      items={items.filter(
+
                        ([, state]) => !isEqual(state, patch.state),
+
                      )}
+
                      {selectedItem}
+
                      state={patch.state}
+
                      save={partial(saveStatus, session.id)} />
+
                  {/if}
                </div>
              {/if}
            {/if}
@@ -932,7 +949,10 @@
        </div>
      </div>
      <LabelInput
-
        locallyAuthenticated={Boolean(session)}
+
        locallyAuthenticated={role.isDelegate(
+
          session?.publicKey,
+
          project.delegates,
+
        )}
        submitInProgress={labelState === "submit"}
        labels={patch.labels}
        on:save={async ({ detail: newLabels }) => {