mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-02 09:46:06 +02:00
fix(backend): correct invalid schema format specifying only required for anyOf (#16089)
* fix(backend): correct invalid schema format specifying only `required` for `anyOf` * refactor(backend): make types derived from `allOf` or `anyOf` more strong
This commit is contained in:
@@ -47,23 +47,38 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: ['username', 'host'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
},
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['userId'] },
|
||||
{ required: ['username', 'host'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -85,9 +100,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy(ps.userId != null
|
||||
const user = await this.usersRepository.findOneBy('userId' in ps
|
||||
? { id: ps.userId }
|
||||
: { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
|
||||
: { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
|
||||
|
||||
if (user == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
|
||||
@@ -54,25 +54,39 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: ['username', 'host'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
birthday: { ...birthdaySchema, nullable: true },
|
||||
},
|
||||
},
|
||||
|
||||
birthday: { ...birthdaySchema, nullable: true },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['userId'] },
|
||||
{ required: ['username', 'host'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -94,9 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy(ps.userId != null
|
||||
const user = await this.usersRepository.findOneBy('userId' in ps
|
||||
? { id: ps.userId }
|
||||
: { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
|
||||
: { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
|
||||
|
||||
if (user == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
|
||||
@@ -114,7 +114,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: {
|
||||
anyOf: [
|
||||
oneOf: [
|
||||
{ type: 'string', format: 'misskey:id' },
|
||||
{
|
||||
type: 'array',
|
||||
|
||||
@@ -26,17 +26,32 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
detail: { type: 'boolean', default: true },
|
||||
|
||||
username: { type: 'string', nullable: true },
|
||||
host: { type: 'string', nullable: true },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['username'] },
|
||||
{ required: ['host'] },
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['username'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['host'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
detail: { type: 'boolean', default: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -47,8 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
) {
|
||||
super(meta, paramDef, (ps, me) => {
|
||||
return this.userSearchService.searchByUsernameAndHost({
|
||||
username: ps.username,
|
||||
host: ps.host,
|
||||
username: 'username' in ps ? ps.username : undefined,
|
||||
host: 'host' in ps ? ps.host : undefined,
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
detail: ps.detail,
|
||||
|
||||
23
packages/backend/src/server/api/endpoints/users/show.test.ts
Normal file
23
packages/backend/src/server/api/endpoints/users/show.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { getValidator } from '../../../../../test/prelude/get-api-validator.js';
|
||||
import { paramDef } from './show.js';
|
||||
|
||||
const VALID = true;
|
||||
const INVALID = false;
|
||||
|
||||
describe('api:users/show', () => {
|
||||
describe('validation', () => {
|
||||
const v = getValidator(paramDef);
|
||||
|
||||
test('Reject empty', () => expect(v({})).toBe(INVALID));
|
||||
test('Reject host only', () => expect(v({ host: 'misskey.test' })).toBe(INVALID));
|
||||
test('Accept userId only', () => expect(v({ userId: '1' })).toBe(VALID));
|
||||
test('Accept username and host', () => expect(v({ username: 'alice', host: 'misskey.test' })).toBe(VALID));
|
||||
});
|
||||
});
|
||||
@@ -59,23 +59,44 @@ export const meta = {
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
userIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
allOf: [
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
userIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
},
|
||||
required: ['userIds'],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
},
|
||||
required: ['username'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['userId'] },
|
||||
{ required: ['userIds'] },
|
||||
{ required: ['username'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
@@ -102,9 +123,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
let user;
|
||||
|
||||
const isModerator = await this.roleService.isModerator(me);
|
||||
ps.username = ps.username?.trim();
|
||||
if ('username' in ps) {
|
||||
ps.username = ps.username.trim();
|
||||
}
|
||||
|
||||
if (ps.userIds) {
|
||||
if ('userIds' in ps) {
|
||||
if (ps.userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -129,7 +152,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
return _users.map(u => _userMap.get(u.id)!);
|
||||
} else {
|
||||
// Lookup user
|
||||
if (typeof ps.host === 'string' && typeof ps.username === 'string') {
|
||||
if (typeof ps.host === 'string' && 'username' in ps) {
|
||||
if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
@@ -139,7 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.failedToResolveRemoteUser);
|
||||
});
|
||||
} else {
|
||||
const q: FindOptionsWhere<MiUser> = ps.userId != null
|
||||
const q: FindOptionsWhere<MiUser> = 'userId' in ps
|
||||
? { id: ps.userId }
|
||||
: { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user