diff options
| author | zyoshoka <107108195+zyoshoka@users.noreply.github.com> | 2025-05-27 08:57:09 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-27 08:57:09 +0900 |
| commit | d27075c5f536a86a6c81cb3c3cec92b302663e2e (patch) | |
| tree | c9ab17782c4bc77da1f534c8731bbad5a0000849 /packages/backend/src/server/api/endpoints/users | |
| parent | fix(backend): add response schema for `notes/show-partial-bulk` endpoint (#16... (diff) | |
| download | misskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.tar.gz misskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.tar.bz2 misskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.zip | |
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
Diffstat (limited to 'packages/backend/src/server/api/endpoints/users')
6 files changed, 162 insertions, 72 deletions
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index a8b4319a61..bb8d4c49e9 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -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); diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index feda5bb353..1fc87151b2 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -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); diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 1d75437b81..f146095cf1 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -114,7 +114,7 @@ export const paramDef = { type: 'object', properties: { userId: { - anyOf: [ + oneOf: [ { type: 'string', format: 'misskey:id' }, { type: 'array', diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 134f1a8e87..d1d6354d53 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -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, diff --git a/packages/backend/src/server/api/endpoints/users/show.test.ts b/packages/backend/src/server/api/endpoints/users/show.test.ts new file mode 100644 index 0000000000..068ffd8bc9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/show.test.ts @@ -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)); + }); +}); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 431869d47f..d57db42e6d 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -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() }; |