fix: チャンネルの編集権限をチャンネル所有者とモデレーターに限定する (#10268)

* チャンネルの編集権限をチャンネルオーナーとモデレーターに限定する

* PR 指摘点対応(共有ボタンを全員に表示、$i の nullable 対応、fix a typo)

* everyOne -> share
This commit is contained in:
mmorita 2023-03-09 07:58:53 +09:00 committed by GitHub
parent 97a43bc30f
commit aad70a97db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 18 deletions

View file

@ -4,6 +4,7 @@ import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = { export const meta = {
tags: ['channels'], tags: ['channels'],
@ -61,6 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private driveFilesRepository: DriveFilesRepository, private driveFilesRepository: DriveFilesRepository,
private channelEntityService: ChannelEntityService, private channelEntityService: ChannelEntityService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({ const channel = await this.channelsRepository.findOneBy({
@ -71,7 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchChannel); throw new ApiError(meta.errors.noSuchChannel);
} }
if (channel.userId !== me.id) { const iAmModerator = await this.roleService.isModerator(me);
if (channel.userId !== me.id && !iAmModerator) {
throw new ApiError(meta.errors.accessDenied); throw new ApiError(meta.errors.accessDenied);
} }

View file

@ -46,7 +46,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { useRouter } from '@/router'; import { useRouter } from '@/router';
import { $i } from '@/account'; import { $i, iAmModerator } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
@ -90,7 +90,9 @@ function openPostForm() {
}); });
} }
const headerActions = $computed(() => channel && channel.userId ? [{ const headerActions = $computed(() => {
if (channel && channel.userId) {
const share = {
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.share, text: i18n.ts.share,
handler: async (): Promise<void> => { handler: async (): Promise<void> => {
@ -100,11 +102,18 @@ const headerActions = $computed(() => channel && channel.userId ? [{
url: `${url}/channels/${channel.id}`, url: `${url}/channels/${channel.id}`,
}); });
}, },
}, { };
const canEdit = ($i && $i.id === channel.userId) || iAmModerator;
return canEdit ? [share, {
icon: 'ti ti-settings', icon: 'ti ti-settings',
text: i18n.ts.edit, text: i18n.ts.edit,
handler: edit, handler: edit,
}] : null); }] : [share];
} else {
return null;
}
});
const headerTabs = $computed(() => [{ const headerTabs = $computed(() => [{
key: 'overview', key: 'overview',