merge: upstream

This commit is contained in:
Mar0xy 2023-09-26 02:26:30 +02:00
commit 8595a325ce
No known key found for this signature in database
GPG key ID: 56569BBE47D2C828
145 changed files with 3208 additions and 1285 deletions

View file

@ -20,6 +20,7 @@ import type { MiLocalUser } from '@/models/User.js';
import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js';
import { WebAuthnService } from '@/core/WebAuthnService.js';
import { UserAuthService } from '@/core/UserAuthService.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/typescript-types';
@ -43,6 +44,7 @@ export class SigninApiService {
private idService: IdService,
private rateLimiterService: RateLimiterService,
private signinService: SigninService,
private userAuthService: UserAuthService,
private webAuthnService: WebAuthnService,
) {
}
@ -125,7 +127,7 @@ export class SigninApiService {
const same = await argon2.verify(profile.password!, password);
const fail = async (status?: number, failure?: { id: string }) => {
// Append signin history
// Append signin history
await this.signinsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
@ -155,27 +157,15 @@ export class SigninApiService {
});
}
if (profile.twoFactorBackupSecret?.includes(token)) {
await this.userProfilesRepository.update({ userId: profile.userId }, {
twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token),
});
return this.signinService.signin(request, reply, user);
}
const delta = OTPAuth.TOTP.validate({
secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
digits: 6,
token,
window: 1,
});
if (delta === null) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
return await fail(403, {
id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f',
});
} else {
return this.signinService.signin(request, reply, user);
}
return this.signinService.signin(request, reply, user);
} else if (body.credential) {
if (!same && !profile.usePasswordLessLogin) {
return await fail(403, {
@ -204,6 +194,6 @@ export class SigninApiService {
reply.code(200);
return authRequest;
}
// never get here
// never get here
}
}

View file

@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
userId: ps.userId,
});
}, me);
return packed;
});

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -37,13 +38,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.delete(announcement.id);
await this.announcementService.delete(announcement, me);
});
}
}

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -45,13 +46,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.update(announcement.id, {
await this.announcementService.update(announcement, {
updatedAt: new Date(),
title: ps.title,
text: ps.text,
@ -62,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
isActive: ps.isActive,
});
}, me);
});
}
}

View file

@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { ApiError } from '../../../error.js';
@ -61,7 +60,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
@ -77,11 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
});
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
emojiId: emoji.id,
});
}, me);
return this.emojiEntityService.packDetailed(emoji);
});

View file

@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.deleteBulk(ps.ids);
await this.customEmojiService.deleteBulk(ps.ids, me);
});
}
}

View file

@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.delete(ps.id);
await this.customEmojiService.delete(ps.id, me);
});
}
}

View file

@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
});
}, me);
});
}
}

View file

@ -9,6 +9,7 @@ import type { InstancesRepository } from '@/models/_.js';
import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -34,6 +35,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private utilityService: UtilityService,
private federatedInstanceService: FederatedInstanceService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) });
@ -42,9 +44,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('instance not found');
}
this.federatedInstanceService.update(instance.id, {
await this.federatedInstanceService.update(instance.id, {
isSuspended: ps.isSuspended,
});
if (instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended) {
this.moderationLogService.log(me, 'suspendRemoteInstance', {
id: instance.id,
host: instance.host,
});
} else {
this.moderationLogService.log(me, 'unsuspendRemoteInstance', {
id: instance.id,
host: instance.host,
});
}
}
});
}
}

View file

@ -321,6 +321,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
maintainerEmail: instance.maintainerEmail,
version: this.config.version,
name: instance.name,
shortName: instance.shortName,
uri: this.config.url,
description: instance.description,
langs: instance.langs,

View file

@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
this.queueService.destroy();
this.moderationLogService.insertModerationLog(me, 'clearQueue');
this.moderationLogService.log(me, 'clearQueue');
});
}
}

View file

@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
break;
}
this.moderationLogService.insertModerationLog(me, 'promoteQueue');
this.moderationLogService.log(me, 'promoteQueue');
});
}
}

View file

@ -10,6 +10,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -47,8 +48,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps) => {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
@ -70,6 +73,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
password: hash,
});
this.moderationLogService.log(me, 'resetPassword', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
return {
password: passwd,
};

View file

@ -10,6 +10,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
import { QueueService } from '@/core/QueueService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -41,6 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queueService: QueueService,
private instanceActorService: InstanceActorService,
private apRendererService: ApRendererService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
@ -61,6 +63,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
assigneeId: me.id,
forwarded: ps.forward && report.targetUserHost != null,
});
this.moderationLogService.log(me, 'resolveAbuseReport', {
reportId: report.id,
report: report,
forwarded: ps.forward && report.targetUserHost != null,
});
});
}
}

View file

@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return;
}
await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null);
await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null, me);
});
}
}

View file

@ -5,11 +5,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin', 'role'],
@ -58,37 +55,11 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService,
private idService: IdService,
private roleEntityService: RoleEntityService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
const date = new Date();
const created = await this.rolesRepository.insert({
id: this.idService.genId(),
createdAt: date,
updatedAt: date,
lastUsedAt: date,
name: ps.name,
description: ps.description,
color: ps.color,
iconUrl: ps.iconUrl,
target: ps.target,
condFormula: ps.condFormula,
isPublic: ps.isPublic,
isAdministrator: ps.isAdministrator,
isModerator: ps.isModerator,
isExplorable: ps.isExplorable,
asBadge: ps.asBadge,
canEditMembersByModerator: ps.canEditMembersByModerator,
displayOrder: ps.displayOrder,
policies: ps.policies,
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
this.globalEventService.publishInternalEvent('roleCreated', created);
const created = await this.roleService.create(ps, me);
return await this.roleEntityService.pack(created, me);
});

View file

@ -6,9 +6,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin', 'role'],
@ -41,17 +41,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps) => {
super(meta, paramDef, async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) {
throw new ApiError(meta.errors.noSuchRole);
}
await this.rolesRepository.delete({
id: ps.roleId,
});
this.globalEventService.publishInternalEvent('roleDeleted', role);
await this.roleService.delete(role, me);
});
}
}

View file

@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchUser);
}
await this.roleService.unassign(user.id, role.id);
await this.roleService.unassign(user.id, role.id, me);
});
}
}

View file

@ -9,6 +9,7 @@ import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin', 'role'],
@ -70,17 +71,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps) => {
const roleExist = await this.rolesRepository.exist({ where: { id: ps.roleId } });
if (!roleExist) {
super(meta, paramDef, async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) {
throw new ApiError(meta.errors.noSuchRole);
}
const date = new Date();
await this.rolesRepository.update(ps.roleId, {
updatedAt: date,
await this.roleService.update(role, {
name: ps.name,
description: ps.description,
color: ps.color,
@ -95,9 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
canEditMembersByModerator: ps.canEditMembersByModerator,
displayOrder: ps.displayOrder,
policies: ps.policies,
});
const updated = await this.rolesRepository.findOneByOrFail({ id: ps.roleId });
this.globalEventService.publishInternalEvent('roleUpdated', updated);
}, me);
});
}
}

View file

@ -62,6 +62,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
type: { type: 'string', nullable: true },
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],
} as const;
@ -78,6 +80,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
if (ps.type != null) {
query.andWhere('report.type = :type', { type: ps.type });
}
if (ps.userId != null) {
query.andWhere('report.userId = :userId', { userId: ps.userId });
}
const reports = await query.limit(ps.limit).getMany();
return await this.moderationLogEntityService.packMany(reports);

View file

@ -60,8 +60,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSuspended: true,
});
this.moderationLogService.insertModerationLog(me, 'suspend', {
targetId: user.id,
this.moderationLogService.log(me, 'suspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
(async () => {

View file

@ -45,8 +45,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSuspended: false,
});
this.moderationLogService.insertModerationLog(me, 'unsuspend', {
targetId: user.id,
this.moderationLogService.log(me, 'unsuspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
this.userSuspendService.doPostUnsuspend(user);

View file

@ -44,6 +44,7 @@ export const paramDef = {
backgroundImageUrl: { type: 'string', nullable: true },
logoImageUrl: { type: 'string', nullable: true },
name: { type: 'string', nullable: true },
shortName: { type: 'string', nullable: true },
description: { type: 'string', nullable: true },
defaultLightTheme: { type: 'string', nullable: true },
defaultDarkTheme: { type: 'string', nullable: true },
@ -188,6 +189,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.name = ps.name;
}
if (ps.shortName !== undefined) {
set.shortName = ps.shortName;
}
if (ps.description !== undefined) {
set.description = ps.description;
}
@ -436,8 +441,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.manifestJsonOverride = ps.manifestJsonOverride;
}
const before = await this.metaService.fetch(true);
await this.metaService.update(set);
this.moderationLogService.insertModerationLog(me, 'updateMeta');
const after = await this.metaService.fetch(true);
this.moderationLogService.log(me, 'updateServerSettings', {
before,
after,
});
});
}
}

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -32,6 +33,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -40,9 +43,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('user not found');
}
const currentProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
await this.userProfilesRepository.update({ userId: user.id }, {
moderationNote: ps.text,
});
this.moderationLogService.log(me, 'updateUserNote', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
before: currentProfile.moderationNote,
after: ps.text,
});
});
}
}

View file

@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId)
.where('announcement.isActive = :isActive', { isActive: ps.isActive })
.andWhere('announcement.isActive = :isActive', { isActive: ps.isActive })
.andWhere(new Brackets(qb => {
if (me) qb.orWhere('announcement.userId = :meId', { meId: me.id });
qb.orWhere('announcement.userId IS NULL');

View file

@ -47,7 +47,7 @@ export const paramDef = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'users_blacklist'] },
userListId: { type: 'string', format: 'misskey:id', nullable: true },
keywords: { type: 'array', items: {
type: 'array', items: {

View file

@ -46,7 +46,7 @@ export const paramDef = {
properties: {
antennaId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 },
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'users_blacklist'] },
userListId: { type: 'string', format: 'misskey:id', nullable: true },
keywords: { type: 'array', items: {
type: 'array', items: {

View file

@ -65,11 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.accessDenied);
}
// Delete
await this.driveService.deleteFile(file);
// Publish fileDeleted event
this.globalEventService.publishDriveStream(me.id, 'fileDeleted', file.id);
await this.driveService.deleteFile(file, false, me);
});
}
}

View file

@ -4,12 +4,11 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { DriveService } from '@/core/DriveService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -77,16 +76,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.driveFoldersRepository)
private driveFoldersRepository: DriveFoldersRepository,
private driveFileEntityService: DriveFileEntityService,
private driveService: DriveService,
private roleService: RoleService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw;
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
}
@ -95,49 +89,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.accessDenied);
}
if (ps.name) file.name = ps.name;
if (!this.driveFileEntityService.validateFileName(file.name)) {
throw new ApiError(meta.errors.invalidFileName);
}
let packedFile;
if (ps.comment !== undefined) file.comment = ps.comment;
if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) {
throw new ApiError(meta.errors.restrictedByRole);
}
if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
if (ps.folderId !== undefined) {
if (ps.folderId === null) {
file.folderId = null;
try {
packedFile = await this.driveService.updateFile(file, {
folderId: ps.folderId,
name: ps.name,
isSensitive: ps.isSensitive,
comment: ps.comment,
}, me);
} catch (e) {
if (e instanceof DriveService.InvalidFileNameError) {
throw new ApiError(meta.errors.invalidFileName);
} else if (e instanceof DriveService.NoSuchFolderError) {
throw new ApiError(meta.errors.noSuchFolder);
} else if (e instanceof DriveService.CannotUnmarkSensitiveError) {
throw new ApiError(meta.errors.restrictedByRole);
} else {
const folder = await this.driveFoldersRepository.findOneBy({
id: ps.folderId,
userId: me.id,
});
if (folder == null) {
throw new ApiError(meta.errors.noSuchFolder);
}
file.folderId = folder.id;
throw e;
}
}
await this.driveFilesRepository.update(file.id, {
name: file.name,
comment: file.comment,
folderId: file.folderId,
isSensitive: file.isSensitive,
});
const fileObj = await this.driveFileEntityService.pack(file, { self: true });
// Publish fileUpdated event
this.globalEventService.publishDriveStream(me.id, 'fileUpdated', fileObj);
return fileObj;
return packedFile;
});
}
}

View file

@ -75,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
summary: ps.summary,
script: ps.script,
permissions: ps.permissions,
visibility: ps.visibility,
});
});
}

View file

@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
secret: OTPAuth.Secret.fromBase32(profile.twoFactorTempSecret),
digits: 6,
token,
window: 1,
window: 5,
});
if (delta === null) {

View file

@ -13,6 +13,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
import { WebAuthnService } from '@/core/WebAuthnService.js';
import { ApiError } from '@/server/api/error.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -38,6 +39,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
name: { type: 'string', minLength: 1, maxLength: 30 },
credential: { type: 'object' },
},
@ -55,16 +57,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private userSecurityKeysRepository: UserSecurityKeysRepository,
private webAuthnService: WebAuthnService,
private userAuthService: UserAuthService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password ?? '', ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);;
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -11,6 +11,7 @@ import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { WebAuthnService } from '@/core/WebAuthnService.js';
import { ApiError } from '@/server/api/error.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -42,6 +43,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
},
required: ['password'],
} as const;
@ -54,8 +56,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private userProfilesRepository: UserProfilesRepository,
private webAuthnService: WebAuthnService,
private userAuthService: UserAuthService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOne({
where: {
userId: me.id,
@ -68,9 +72,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
// Compare password
const same = await argon2.verify(profile.password ?? '', ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);;
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -13,6 +13,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { ApiError } from '@/server/api/error.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -32,6 +33,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
},
required: ['password'],
} as const;
@ -44,14 +46,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private userAuthService: UserAuthService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password ?? '', ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -12,6 +12,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -31,6 +32,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
credentialId: { type: 'string' },
},
required: ['password', 'credentialId'],
@ -46,15 +48,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userProfilesRepository: UserProfilesRepository,
private userEntityService: UserEntityService,
private userAuthService: UserAuthService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password ?? '', ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -12,6 +12,7 @@ import type { UserProfilesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -31,6 +32,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
},
required: ['password'],
} as const;
@ -42,15 +44,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userProfilesRepository: UserProfilesRepository,
private userEntityService: UserEntityService,
private userAuthService: UserAuthService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password ?? '', ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -9,6 +9,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -21,6 +22,7 @@ export const paramDef = {
properties: {
currentPassword: { type: 'string' },
newPassword: { type: 'string', minLength: 1 },
token: { type: 'string', nullable: true },
},
required: ['currentPassword', 'newPassword'],
} as const;
@ -30,14 +32,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private userAuthService: UserAuthService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password!, ps.currentPassword);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password!, ps.currentPassword);
if (!passwordMatched) {
throw new Error('incorrect password');
}

View file

@ -10,6 +10,7 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
import { DI } from '@/di-symbols.js';
import { UserAuthService } from '@/core/UserAuthService.js';
export const meta = {
requireCredential: true,
@ -21,6 +22,7 @@ export const paramDef = {
type: 'object',
properties: {
password: { type: 'string' },
token: { type: 'string', nullable: true },
},
required: ['password'],
} as const;
@ -34,19 +36,32 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private userAuthService: UserAuthService,
private deleteAccountService: DeleteAccountService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id });
if (userDetailed.isDeleted) {
return;
}
// Compare password
const same = await argon2.verify(profile.password!, ps.password);
if (!same) {
const passwordMatched = await argon2.verify(profile.password!, ps.password);
if (!passwordMatched) {
throw new Error('incorrect password');
}

View file

@ -15,6 +15,7 @@ import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
import { UserAuthService } from '@/core/UserAuthService.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -47,6 +48,7 @@ export const paramDef = {
properties: {
password: { type: 'string' },
email: { type: 'string', nullable: true },
token: { type: 'string', nullable: true },
},
required: ['password'],
} as const;
@ -62,15 +64,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userEntityService: UserEntityService,
private emailService: EmailService,
private userAuthService: UserAuthService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const token = ps.token;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
// Compare password
const same = await argon2.verify(profile.password!, ps.password);
if (profile.twoFactorEnabled) {
if (token == null) {
throw new Error('authentication failed');
}
if (!same) {
try {
await this.userAuthService.twoFactorAuthenticate(profile, token);
} catch (e) {
throw new Error('authentication failed');
}
}
const passwordMatched = await argon2.verify(profile.password!, ps.password);;
if (!passwordMatched) {
throw new ApiError(meta.errors.incorrectPassword);
}

View file

@ -40,6 +40,10 @@ export const meta = {
type: 'string',
optional: false, nullable: false,
},
shortName: {
type: 'string',
optional: false, nullable: true,
},
uri: {
type: 'string',
optional: false, nullable: false,
@ -288,6 +292,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
version: this.config.version,
name: instance.name,
shortName: instance.shortName,
uri: this.config.url,
description: instance.description,
langs: instance.langs,

View file

@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note);
await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note, false, me);
});
}
}

View file

@ -4,12 +4,13 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets, type FindOptionsWhere } from 'typeorm';
import type { NoteReactionsRepository } from '@/models/_.js';
import type { MiNoteReaction } from '@/models/NoteReaction.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
import { DI } from '@/di-symbols.js';
import type { FindOptionsWhere } from 'typeorm';
import { QueryService } from '@/core/QueryService.js';
export const meta = {
tags: ['notes', 'reactions'],
@ -44,7 +45,6 @@ export const paramDef = {
noteId: { type: 'string', format: 'misskey:id' },
type: { type: 'string', nullable: true },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
@ -58,29 +58,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteReactionsRepository: NoteReactionsRepository,
private noteReactionEntityService: NoteReactionEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = {
noteId: ps.noteId,
} as FindOptionsWhere<MiNoteReaction>;
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId)
.andWhere('reaction.noteId = :noteId', { noteId: ps.noteId })
.leftJoinAndSelect('reaction.user', 'user')
.leftJoinAndSelect('reaction.note', 'note');
if (ps.type) {
// ローカルリアクションはホスト名が . とされているが
// DB 上ではそうではないので、必要に応じて変換
const suffix = '@.:';
const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type;
query.reaction = type;
query.andWhere('reaction.reaction = :type', { type });
}
const reactions = await this.noteReactionsRepository.find({
where: query,
take: ps.limit,
skip: ps.offset,
order: {
id: -1,
},
relations: ['user', 'note'],
});
const reactions = await query.limit(ps.limit).getMany();
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));
});

View file

@ -114,10 +114,10 @@ export class ClientServerService {
let manifest = {
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'short_name': instance.name || 'Misskey',
'short_name': instance.shortName || instance.name || this.config.host,
// 空文字列の場合右辺を使いたいため
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
'name': instance.name || 'Misskey',
'name': instance.name || this.config.host,
'start_url': '/',
'display': 'standalone',
'background_color': '#313a42',

View file

@ -35,7 +35,7 @@ html
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.32.0')
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.35.0')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists