diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ae535735b6..4a1a2d68ef 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -957,6 +957,7 @@ _role: gtlAvailable: "グローバルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧" canPublicNote: "パブリック投稿の許可" + canInvite: "インスタンス招待コードの発行" driveCapacity: "ドライブ容量" antennaMax: "アンテナの作成可能数" _condition: diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 3183adb369..78524c833d 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -16,6 +16,7 @@ export type RoleOptions = { gtlAvailable: boolean; ltlAvailable: boolean; canPublicNote: boolean; + canInvite: boolean; driveCapacityMb: number; antennaLimit: number; }; @@ -24,6 +25,7 @@ export const DEFAULT_ROLE: RoleOptions = { gtlAvailable: true, ltlAvailable: true, canPublicNote: true, + canInvite: false, driveCapacityMb: 100, antennaLimit: 5, }; @@ -179,6 +181,7 @@ export class RoleService implements OnApplicationShutdown { gtlAvailable: getOptionValues('gtlAvailable').some(x => x === true), ltlAvailable: getOptionValues('ltlAvailable').some(x => x === true), canPublicNote: getOptionValues('canPublicNote').some(x => x === true), + canInvite: getOptionValues('canInvite').some(x => x === true), driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')), antennaLimit: Math.max(...getOptionValues('antennaLimit')), }; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index c226c4e93c..aa88a9dd13 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -37,7 +37,7 @@ import * as ep___admin_federation_updateInstance from './endpoints/admin/federat import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___admin_invite from './endpoints/admin/invite.js'; +import * as ep___invite from './endpoints/invite.js'; import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; @@ -371,7 +371,7 @@ const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federati const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default }; const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default }; const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default }; -const $admin_invite: Provider = { provide: 'ep:admin/invite', useClass: ep___admin_invite.default }; +const $invite: Provider = { provide: 'ep:invite', useClass: ep___invite.default }; const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default }; const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default }; const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default }; @@ -709,7 +709,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_getIndexStats, $admin_getTableStats, $admin_getUserIps, - $admin_invite, + $invite, $admin_promo_create, $admin_queue_clear, $admin_queue_deliverDelayed, @@ -1041,7 +1041,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_getIndexStats, $admin_getTableStats, $admin_getUserIps, - $admin_invite, + $invite, $admin_promo_create, $admin_queue_clear, $admin_queue_deliverDelayed, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 1df3240e41..0a26094c44 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -36,7 +36,7 @@ import * as ep___admin_federation_updateInstance from './endpoints/admin/federat import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___admin_invite from './endpoints/admin/invite.js'; +import * as ep___invite from './endpoints/invite.js'; import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; @@ -368,7 +368,7 @@ const eps = [ ['admin/get-index-stats', ep___admin_getIndexStats], ['admin/get-table-stats', ep___admin_getTableStats], ['admin/get-user-ips', ep___admin_getUserIps], - ['admin/invite', ep___admin_invite], + ['invite', ep___invite], ['admin/promo/create', ep___admin_promo_create], ['admin/queue/clear', ep___admin_queue_clear], ['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed], diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/invite.ts similarity index 80% rename from packages/backend/src/server/api/endpoints/admin/invite.ts rename to packages/backend/src/server/api/endpoints/invite.ts index bc42bf792a..d22946e04a 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/invite.ts @@ -4,12 +4,12 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { - tags: ['admin'], + tags: ['meta'], requireCredential: true, - requireModerator: true, res: { type: 'object', @@ -39,9 +39,15 @@ export default class extends Endpoint { @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, + private roleService: RoleService, private idService: IdService, ) { - super(meta, paramDef, async () => { + super(meta, paramDef, async (ps, me) => { + const role = await this.roleService.getUserRoleOptions(me.id); + if (!me.isRoot && !role.canInvite) { + throw new Error('access denied'); + } + const code = rndstr({ length: 8, chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns) diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 83f3c10d1d..0166960ff6 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -80,7 +80,7 @@ const menuDef = $computed(() => [{ action: lookup, }, ...(instance.disableRegistration ? [{ type: 'button', - icon: 'ti ti-user', + icon: 'ti ti-user-plus', text: i18n.ts.invite, action: invite, }] : [])], @@ -223,7 +223,7 @@ provideMetadataReceiver((info) => { }); const invite = () => { - os.api('admin/invite').then(x => { + os.api('invite').then(x => { os.alert({ type: 'info', text: x.code, diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index e1efb8dcec..1d83ae3244 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -77,6 +77,19 @@ + + + +
+ + + + + + +
+
+ @@ -160,6 +173,8 @@ let options_ltlAvailable_useDefault = $ref(role?.options?.ltlAvailable?.useDefau let options_ltlAvailable_value = $ref(role?.options?.ltlAvailable?.value ?? false); let options_canPublicNote_useDefault = $ref(role?.options?.canPublicNote?.useDefault ?? true); let options_canPublicNote_value = $ref(role?.options?.canPublicNote?.value ?? false); +let options_canInvite_useDefault = $ref(role?.options?.canInvite?.useDefault ?? true); +let options_canInvite_value = $ref(role?.options?.canInvite?.value ?? false); let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.useDefault ?? true); let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0); let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true); @@ -176,6 +191,7 @@ function getOptions() { gtlAvailable: { useDefault: options_gtlAvailable_useDefault, value: options_gtlAvailable_value }, ltlAvailable: { useDefault: options_ltlAvailable_useDefault, value: options_ltlAvailable_value }, canPublicNote: { useDefault: options_canPublicNote_useDefault, value: options_canPublicNote_value }, + canInvite: { useDefault: options_canInvite_useDefault, value: options_canInvite_value }, driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value }, antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value }, }; diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index f74a3dcf5a..6495074bb7 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -32,6 +32,14 @@ + + + + + + + + @@ -81,6 +89,7 @@ const roles = await os.api('admin/roles/list'); let options_gtlAvailable = $ref(instance.baseRole.gtlAvailable); let options_ltlAvailable = $ref(instance.baseRole.ltlAvailable); let options_canPublicNote = $ref(instance.baseRole.canPublicNote); +let options_canInvite = $ref(instance.baseRole.canInvite); let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); let options_antennaLimit = $ref(instance.baseRole.antennaLimit); @@ -90,6 +99,7 @@ async function updateBaseRole() { gtlAvailable: options_gtlAvailable, ltlAvailable: options_ltlAvailable, canPublicNote: options_canPublicNote, + canInvite: options_canInvite, driveCapacityMb: options_driveCapacityMb, antennaLimit: options_antennaLimit, }, diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index c63e962a8d..079b31489e 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -2,6 +2,7 @@ import * as os from '@/os'; import { instance } from '@/instance'; import { host } from '@/config'; import { i18n } from '@/i18n'; +import { $i } from '@/account'; export function openInstanceMenu(ev: MouseEvent) { os.popupMenu([{ @@ -46,7 +47,23 @@ export function openInstanceMenu(ev: MouseEvent) { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', - }], + }, ($i && ($i.isRoot || $i.role.canInvite) && instance.disableRegistration) ? { + text: i18n.ts.invite, + icon: 'ti ti-user-plus', + action: () => { + os.api('invite').then(x => { + os.alert({ + type: 'info', + text: x.code, + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); + }, + } : undefined], }, null, { text: i18n.ts.help, icon: 'ti ti-question-circle',