mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-27 23:50:36 +08:00
Add permission checks for delegates and authors
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user