server: improve error messages

Refactor Error's to ApiError's.

Changelog: Changed
This commit is contained in:
Johann150 2022-12-25 15:33:46 +01:00
parent 09bc3cf95a
commit c2372315f7
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
26 changed files with 151 additions and 62 deletions

View file

@ -1,5 +1,6 @@
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { signup } from '../../../common/signup.js'; import { signup } from '../../../common/signup.js';
@ -17,6 +18,8 @@ export const meta = {
}, },
}, },
}, },
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -31,10 +34,17 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, _me) => { export default define(meta, paramDef, async (ps, _me) => {
const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null; const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null;
const noUsers = (await Users.countBy({ if (me == null) {
host: IsNull(), // check if this is the initial setup
})) === 0; const noUsers = (await Users.countBy({
if (!noUsers && !me?.isAdmin) throw new Error('access denied'); host: IsNull(),
})) === 0;
if (!noUsers) {
throw new ApiError('ACCESS_DENIED');
}
} else if (!me.isAdmin) {
throw new ApiError('ACCESS_DENIED');
}
const { account, secret } = await signup({ const { account, secret } = await signup({
username: ps.username, username: ps.username,

View file

@ -1,4 +1,5 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { doPostSuspend } from '@/services/suspend-user.js'; import { doPostSuspend } from '@/services/suspend-user.js';
import { publishUserEvent } from '@/services/stream.js'; import { publishUserEvent } from '@/services/stream.js';
import { createDeleteAccountJob } from '@/queue/index.js'; import { createDeleteAccountJob } from '@/queue/index.js';
@ -9,6 +10,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_USER', 'IS_ADMIN', 'IS_MODERATOR'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -24,15 +27,11 @@ export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} } else if (user.isAdmin) {
throw new ApiError('IS_ADMIN');
if (user.isAdmin) { } else if(user.isModerator) {
throw new Error('cannot suspend admin'); throw new ApiError('IS_MODERATOR');
}
if (user.isModerator) {
throw new Error('cannot suspend moderator');
} }
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {

View file

@ -1,5 +1,6 @@
import { Instances } from '@/models/index.js'; import { Instances } from '@/models/index.js';
import { toPuny } from '@/misc/convert-host.js'; import { toPuny } from '@/misc/convert-host.js';
import { ApiError } from '@/server/api/error.js';
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
import define from '../../../define.js'; import define from '../../../define.js';
@ -8,6 +9,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_OBJECT'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,7 +26,7 @@ export default define(meta, paramDef, async (ps, me) => {
const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); const instance = await Instances.findOneBy({ host: toPuny(ps.host) });
if (instance == null) { if (instance == null) {
throw new Error('instance not found'); throw new ApiError('NO_SUCH_OBJECT');
} }
fetchInstanceMetadata(instance, true); fetchInstanceMetadata(instance, true);

View file

@ -1,5 +1,6 @@
import { Instances } from '@/models/index.js'; import { Instances } from '@/models/index.js';
import { toPuny } from '@/misc/convert-host.js'; import { toPuny } from '@/misc/convert-host.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
@ -7,6 +8,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_OBJECT'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,7 +26,7 @@ export default define(meta, paramDef, async (ps, me) => {
const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); const instance = await Instances.findOneBy({ host: toPuny(ps.host) });
if (instance == null) { if (instance == null) {
throw new Error('instance not found'); throw new ApiError('NO_SUCH_OBJECT');
} }
Instances.update({ host: toPuny(ps.host) }, { Instances.update({ host: toPuny(ps.host) }, {

View file

@ -1,12 +1,17 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
description: 'Grants a user moderator privileges. Administrators cannot be granted moderator privileges.',
requireCredential: true, requireCredential: true,
requireAdmin: true, requireAdmin: true,
errors: ['NO_SUCH_USER', 'IS_ADMIN'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -22,11 +27,11 @@ export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
if (user.isAdmin) { if (user.isAdmin) {
throw new Error('cannot mark as moderator if admin user'); throw new ApiError('IS_ADMIN');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -1,4 +1,5 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import define from '../../../define.js'; import define from '../../../define.js';
@ -7,6 +8,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireAdmin: true, requireAdmin: true,
errors: ['NO_SUCH_USER'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -22,7 +25,7 @@ export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -51,7 +51,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
try { try {
if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only'); if (new URL(ps.inbox).protocol !== 'https:') throw new ApiError('INVALID_URL', 'https only');
} catch (e) { } catch (e) {
throw new ApiError('INVALID_URL', e); throw new ApiError('INVALID_URL', e);
} }

View file

@ -1,6 +1,7 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import { Users, UserProfiles } from '@/models/index.js'; import { Users, UserProfiles } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js'; import define from '../../define.js';
export const meta = { export const meta = {
@ -21,6 +22,8 @@ export const meta = {
}, },
}, },
}, },
errors: ['NO_SUCH_USER', 'IS_ADMIN'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -36,11 +39,11 @@ export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
if (user.isAdmin) { if (user.isAdmin) {
throw new Error('cannot reset password of admin'); throw new ApiError('IS_ADMIN');
} }
const passwd = secureRndstr(8, true); const passwd = secureRndstr(8, true);

View file

@ -1,4 +1,5 @@
import { Signins, UserProfiles, Users } from '@/models/index.js'; import { Signins, UserProfiles, Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js'; import define from '../../define.js';
export const meta = { export const meta = {
@ -11,6 +12,8 @@ export const meta = {
type: 'object', type: 'object',
nullable: false, optional: false, nullable: false, optional: false,
}, },
errors: ['NO_SUCH_USER', 'IS_ADMIN'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -29,12 +32,12 @@ export default define(meta, paramDef, async (ps, me) => {
]); ]);
if (user == null || profile == null) { if (user == null || profile == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
const _me = await Users.findOneByOrFail({ id: me.id }); const _me = await Users.findOneByOrFail({ id: me.id });
if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) {
throw new Error('cannot show info of admin'); throw new ApiError('IS_ADMIN');
} }
if (!_me.isAdmin) { if (!_me.isAdmin) {

View file

@ -1,4 +1,5 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import define from '../../define.js'; import define from '../../define.js';
@ -8,6 +9,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_USER', 'IS_ADMIN'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,11 +26,11 @@ export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
if (user.isAdmin) { if (user.isAdmin) {
throw new Error('cannot silence admin'); throw new ApiError('IS_ADMIN');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -1,6 +1,7 @@
import deleteFollowing from '@/services/following/delete.js'; import deleteFollowing from '@/services/following/delete.js';
import { Users, Followings, Notifications } from '@/models/index.js'; import { Users, Followings, Notifications } from '@/models/index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { ApiError } from '@/server/api/error.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { doPostSuspend } from '@/services/suspend-user.js'; import { doPostSuspend } from '@/services/suspend-user.js';
import { publishUserEvent } from '@/services/stream.js'; import { publishUserEvent } from '@/services/stream.js';
@ -11,6 +12,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_USER', 'IS_ADMIN', 'IS_MODERATOR'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -26,15 +29,11 @@ export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} } else if (user.isAdmin) {
throw new ApiError('IS_ADMIN');
if (user.isAdmin) { } else if (user.isModerator) {
throw new Error('cannot suspend admin'); throw new ApiError('IS_MODERATOR');
}
if (user.isModerator) {
throw new Error('cannot suspend moderator');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -1,4 +1,5 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import define from '../../define.js'; import define from '../../define.js';
@ -8,6 +9,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_USER'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,7 +26,7 @@ export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -1,4 +1,5 @@
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { doPostUnsuspend } from '@/services/unsuspend-user.js'; import { doPostUnsuspend } from '@/services/unsuspend-user.js';
import define from '../../define.js'; import define from '../../define.js';
@ -8,6 +9,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
errors: ['NO_SUCH_USER'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,7 +26,7 @@ export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await Users.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new Error('user not found'); throw new ApiError('NO_SUCH_USER');
} }
await Users.update(user.id, { await Users.update(user.id, {

View file

@ -3,6 +3,7 @@ import { genId } from '@/misc/gen-id.js';
import { GalleryPost } from '@/models/entities/gallery-post.js'; import { GalleryPost } from '@/models/entities/gallery-post.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { HOUR } from '@/const.js'; import { HOUR } from '@/const.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
@ -46,8 +47,14 @@ export default define(meta, paramDef, async (ps, user) => {
}), }),
))).filter((file): file is DriveFile => file != null); ))).filter((file): file is DriveFile => file != null);
if (files.length === 0) { if (files.length !== ps.fileIds.length) {
throw new Error(); throw new ApiError(
'INVALID_PARAM',
{
param: '#/properties/fileIds/items',
reason: 'contains invalid file IDs',
}
);
} }
const post = await GalleryPosts.insert(new GalleryPost({ const post = await GalleryPosts.insert(new GalleryPost({

View file

@ -1,6 +1,7 @@
import { DriveFiles, GalleryPosts } from '@/models/index.js'; import { DriveFiles, GalleryPosts } from '@/models/index.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { HOUR } from '@/const.js'; import { HOUR } from '@/const.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
@ -20,6 +21,8 @@ export const meta = {
optional: false, nullable: false, optional: false, nullable: false,
ref: 'GalleryPost', ref: 'GalleryPost',
}, },
errors: ['INVALID_PARAM'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -45,8 +48,14 @@ export default define(meta, paramDef, async (ps, user) => {
}), }),
))).filter((file): file is DriveFile => file != null); ))).filter((file): file is DriveFile => file != null);
if (files.length === 0) { if (files.length !== ps.fileIds.length) {
throw new Error(); throw new ApiError(
'INVALID_PARAM',
{
param: '#/properties/fileIds/items',
reason: 'contains invalid file IDs',
}
);
} }
await GalleryPosts.update({ await GalleryPosts.update({

View file

@ -1,11 +1,14 @@
import * as speakeasy from 'speakeasy'; import * as speakeasy from 'speakeasy';
import { UserProfiles } from '@/models/index.js'; import { UserProfiles } from '@/models/index.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { ApiError } from '@/server/api/error.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['INTERNAL_ERROR', 'ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -23,7 +26,7 @@ export default define(meta, paramDef, async (ps, user) => {
const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.twoFactorTempSecret == null) { if (profile.twoFactorTempSecret == null) {
throw new Error('二段階認証の設定が開始されていません'); throw new ApiError('INTERNAL_ERROR', 'Two-step verification has not been initiated.');
} }
const verified = (speakeasy as any).totp.verify({ const verified = (speakeasy as any).totp.verify({
@ -33,7 +36,7 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
if (!verified) { if (!verified) {
throw new Error('not verified'); throw new ApiError('ACCESS_DENIED', 'TOTP missmatch');
} }
await UserProfiles.update(user.id, { await UserProfiles.update(user.id, {

View file

@ -9,6 +9,7 @@ import {
Users, Users,
} from '@/models/index.js'; } from '@/models/index.js';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { ApiError } from '@/server/api/error.js';
import { publishMainStream } from '@/services/stream.js'; import { publishMainStream } from '@/services/stream.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { procedures, hash } from '../../../2fa.js'; import { procedures, hash } from '../../../2fa.js';
@ -20,6 +21,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED', 'INTERNAL_ERROR', 'NO_SUCH_OBJECT'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -42,20 +45,20 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
if (!profile.twoFactorEnabled) { if (!profile.twoFactorEnabled) {
throw new Error('2fa not enabled'); throw new ApiError('INTERNAL_ERROR', '2fa not enabled');
} }
const clientData = JSON.parse(ps.clientDataJSON); const clientData = JSON.parse(ps.clientDataJSON);
if (clientData.type !== 'webauthn.create') { if (clientData.type !== 'webauthn.create') {
throw new Error('not a creation attestation'); throw new ApiError('INTERNAL_ERROR', 'not a creation attestation');
} }
if (clientData.origin !== config.scheme + '://' + config.host) { if (clientData.origin !== config.scheme + '://' + config.host) {
throw new Error('origin mismatch'); throw new ApiError('INTERNAL_ERROR', 'origin mismatch');
} }
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
@ -64,14 +67,14 @@ export default define(meta, paramDef, async (ps, user) => {
const rpIdHash = attestation.authData.slice(0, 32); const rpIdHash = attestation.authData.slice(0, 32);
if (!rpIdHashReal.equals(rpIdHash)) { if (!rpIdHashReal.equals(rpIdHash)) {
throw new Error('rpIdHash mismatch'); throw new ApiError('INTERNAL_ERROR', 'rpIdHash mismatch');
} }
const flags = attestation.authData[32]; const flags = attestation.authData[32];
// eslint:disable-next-line:no-bitwise // eslint:disable-next-line:no-bitwise
if (!(flags & 1)) { if (!(flags & 1)) {
throw new Error('user not present'); throw new ApiError('INTERNAL_ERROR', 'user not present');
} }
const authData = Buffer.from(attestation.authData); const authData = Buffer.from(attestation.authData);
@ -80,11 +83,11 @@ export default define(meta, paramDef, async (ps, user) => {
const publicKeyData = authData.slice(55 + credentialIdLength); const publicKeyData = authData.slice(55 + credentialIdLength);
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData); const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
if (publicKey.get(3) !== -7) { if (publicKey.get(3) !== -7) {
throw new Error('alg mismatch'); throw new ApiError('INTERNAL_ERROR', 'algorithm mismatch');
} }
if (!(procedures as any)[attestation.fmt]) { if (!(procedures as any)[attestation.fmt]) {
throw new Error('unsupported fmt'); throw new ApiError('INTERNAL_ERROR', 'unsupported fmt');
} }
const verificationData = (procedures as any)[attestation.fmt].verify({ const verificationData = (procedures as any)[attestation.fmt].verify({
@ -95,7 +98,7 @@ export default define(meta, paramDef, async (ps, user) => {
publicKey, publicKey,
rpIdHash, rpIdHash,
}); });
if (!verificationData.valid) throw new Error('signature invalid'); if (!verificationData.valid) throw new ApiError('INTERNAL_ERROR', 'signature invalid');
const attestationChallenge = await AttestationChallenges.findOneBy({ const attestationChallenge = await AttestationChallenges.findOneBy({
userId: user.id, userId: user.id,
@ -105,7 +108,7 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
if (!attestationChallenge) { if (!attestationChallenge) {
throw new Error('non-existent challenge'); throw new ApiError('NO_SUCH_OBJECT', 'Attestation challenge not found.');
} }
await AttestationChallenges.delete({ await AttestationChallenges.delete({
@ -118,7 +121,7 @@ export default define(meta, paramDef, async (ps, user) => {
new Date().getTime() - attestationChallenge.createdAt.getTime() >= new Date().getTime() - attestationChallenge.createdAt.getTime() >=
5 * MINUTE 5 * MINUTE
) { ) {
throw new Error('expired challenge'); throw new ApiError('NO_SUCH_OBJECT', 'Attestation challenge expired.');
} }
const credentialIdString = credentialId.toString('hex'); const credentialIdString = credentialId.toString('hex');

View file

@ -3,6 +3,7 @@ import * as crypto from 'node:crypto';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { UserProfiles, AttestationChallenges } from '@/models/index.js'; import { UserProfiles, AttestationChallenges } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js'; import { genId } from '@/misc/gen-id.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
import { hash } from '../../../2fa.js'; import { hash } from '../../../2fa.js';
@ -12,6 +13,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED', 'INTERNAL_ERROR'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -30,11 +33,11 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
if (!profile.twoFactorEnabled) { if (!profile.twoFactorEnabled) {
throw new Error('2fa not enabled'); throw new ApiError('INTERNAL_ERROR', '2fa not enabled');
} }
// 32 byte challenge // 32 byte challenge

View file

@ -3,12 +3,15 @@ import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode'; import * as QRCode from 'qrcode';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { UserProfiles } from '@/models/index.js'; import { UserProfiles } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -27,7 +30,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
// Generate user's secret key // Generate user's secret key

View file

@ -1,12 +1,15 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js'; import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js';
import { publishMainStream } from '@/services/stream.js'; import { publishMainStream } from '@/services/stream.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -26,7 +29,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
// Make sure we only delete the user's own creds // Make sure we only delete the user's own creds

View file

@ -1,11 +1,14 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { UserProfiles } from '@/models/index.js'; import { UserProfiles } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../../define.js'; import define from '../../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -24,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
await UserProfiles.update(user.id, { await UserProfiles.update(user.id, {

View file

@ -1,11 +1,14 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { UserProfiles } from '@/models/index.js'; import { UserProfiles } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js'; import define from '../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -25,7 +28,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.currentPassword, profile.password!); const same = await bcrypt.compare(ps.currentPassword, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
// Generate hash of password // Generate hash of password

View file

@ -1,12 +1,15 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { UserProfiles, Users } from '@/models/index.js'; import { UserProfiles, Users } from '@/models/index.js';
import { deleteAccount } from '@/services/delete-account.js'; import { deleteAccount } from '@/services/delete-account.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js'; import define from '../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -29,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
await deleteAccount(user); await deleteAccount(user);

View file

@ -2,12 +2,15 @@ import bcrypt from 'bcryptjs';
import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js';
import { Users, UserProfiles } from '@/models/index.js'; import { Users, UserProfiles } from '@/models/index.js';
import generateUserToken from '../../common/generate-native-user-token.js'; import generateUserToken from '../../common/generate-native-user-token.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js'; import define from '../../define.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
secure: true, secure: true,
errors: ['ACCESS_DENIED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -29,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) { if (!same) {
throw new Error('incorrect password'); throw new ApiError('ACCESS_DENIED');
} }
const newToken = generateUserToken(); const newToken = generateUserToken();

View file

@ -1,4 +1,5 @@
import { resetDb } from '@/db/postgre.js'; import { resetDb } from '@/db/postgre.js';
import { ApiError } from '@/server/api/error.js';
import define from '../define.js'; import define from '../define.js';
export const meta = { export const meta = {
@ -17,7 +18,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test'); if (process.env.NODE_ENV !== 'test') throw new ApiError('ACCESS_DENIED');
await resetDb(); await resetDb();

View file

@ -187,6 +187,14 @@ export const errors: Record<string, { message: string, httpStatusCode: number }>
message: 'Invalid username.', message: 'Invalid username.',
httpStatusCode: 400, httpStatusCode: 400,
}, },
IS_ADMIN: {
message: 'This action cannot be done to an administrator account.',
httpStatusCode: 400,
},
IS_MODERATOR: {
message: 'This action cannot be done to a moderator account.',
httpStatusCode: 400,
},
LESS_RESTRICTIVE_VISIBILITY: { LESS_RESTRICTIVE_VISIBILITY: {
message: 'The visibility cannot be less restrictive than the parent note.', message: 'The visibility cannot be less restrictive than the parent note.',
httpStatusCode: 400, httpStatusCode: 400,