Compare commits
12 Commits
2025.3.1
...
aba3fc274c
| Author | SHA1 | Date | |
|---|---|---|---|
| aba3fc274c | |||
| 0c84d97cbb | |||
| 34b2f2f748 | |||
| 8fc0a3b590 | |||
| 05fff8d4d6 | |||
| fed33b92e2 | |||
| 6b47cd7b94 | |||
| f82dfdfbd2 | |||
| 5428019061 | |||
| 0ddbeab9c1 | |||
| 6feda8469a | |||
| 191c5f07a4 |
@@ -6,6 +6,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
type CaptchaResponse = {
|
||||
success: boolean;
|
||||
@@ -35,7 +36,7 @@ export class CaptchaService {
|
||||
}, { throwErrorWhenResponseNotOk: false });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`${res.status}`);
|
||||
throw ErrorHandling(`${res.status}`);
|
||||
}
|
||||
|
||||
return await res.json() as CaptchaResponse;
|
||||
@@ -44,48 +45,48 @@ export class CaptchaService {
|
||||
@bindThis
|
||||
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw new Error('recaptcha-failed: no response provided');
|
||||
throw ErrorHandling('recaptcha-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
|
||||
throw new Error(`recaptcha-request-failed: ${err}`);
|
||||
throw ErrorHandling(`recaptcha-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw new Error(`recaptcha-failed: ${errorCodes}`);
|
||||
throw ErrorHandling(`recaptcha-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw new Error('hcaptcha-failed: no response provided');
|
||||
throw ErrorHandling('hcaptcha-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
|
||||
throw new Error(`hcaptcha-request-failed: ${err}`);
|
||||
throw ErrorHandling(`hcaptcha-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw new Error(`hcaptcha-failed: ${errorCodes}`);
|
||||
throw ErrorHandling(`hcaptcha-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw new Error('turnstile-failed: no response provided');
|
||||
throw ErrorHandling('turnstile-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
|
||||
throw new Error(`turnstile-request-failed: ${err}`);
|
||||
throw ErrorHandling(`turnstile-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw new Error(`turnstile-failed: ${errorCodes}`);
|
||||
throw ErrorHandling(`turnstile-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { UsedUsername } from '@/models/entities/UsedUsername.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import generateNativeUserToken from '@/misc/generate-native-user-token.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class CreateSystemUserService {
|
||||
@@ -49,7 +50,7 @@ export class CreateSystemUserService {
|
||||
host: IsNull(),
|
||||
});
|
||||
|
||||
if (exist) throw new Error('the user is already exists');
|
||||
if (exist) throw ErrorHandling('the user is already exists');
|
||||
|
||||
account = await transactionalEntityManager.insert(User, {
|
||||
id: this.idService.genId(),
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { query } from '@/misc/prelude/url.js';
|
||||
import type { Serialized } from '@/server/api/stream/types.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||
|
||||
@@ -107,7 +108,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
}): Promise<void> {
|
||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw ErrorHandling('name already exists');
|
||||
|
||||
await this.emojisRepository.update(emoji.id, {
|
||||
updatedAt: new Date(),
|
||||
|
||||
@@ -42,6 +42,7 @@ import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { correctFilename } from '@/misc/correct-filename.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
type AddFileArgs = {
|
||||
/** User who wish to add file */
|
||||
@@ -535,7 +536,7 @@ export class DriveService {
|
||||
userId: user ? user.id : IsNull(),
|
||||
});
|
||||
|
||||
if (driveFolder == null) throw new Error('folder-not-found');
|
||||
if (driveFolder == null) throw ErrorHandling('folder-not-found');
|
||||
|
||||
return driveFolder;
|
||||
};
|
||||
@@ -750,9 +751,13 @@ export class DriveService {
|
||||
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
||||
return;
|
||||
} else {
|
||||
throw new Error(`Failed to delete the file from the object storage with the given key: ${key}`, {
|
||||
cause: err,
|
||||
});
|
||||
const error = new Error(`Failed to delete the file from the object storage with the given key: ${key}`);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(error, 'stack', { value: '' });
|
||||
Object.defineProperty(err, 'stack', { value: '' });
|
||||
}
|
||||
error['cause'] = err;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
|
||||
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { parseUlid } from '@/misc/id/ulid.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class IdService {
|
||||
@@ -35,7 +36,7 @@ export class IdService {
|
||||
case 'meidg': return genMeidg(date);
|
||||
case 'ulid': return ulid(date.getTime());
|
||||
case 'objectid': return genObjectId(date);
|
||||
default: throw new Error('unrecognized id generation method');
|
||||
default: throw ErrorHandling('unrecognized id generation method');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ export class IdService {
|
||||
case 'meid': return parseMeid(id);
|
||||
case 'meidg': return parseMeidg(id);
|
||||
case 'ulid': return parseUlid(id);
|
||||
default: throw new Error('unrecognized id generation method');
|
||||
default: throw ErrorHandling('unrecognized id generation method');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||
|
||||
@@ -251,7 +252,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
|
||||
if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) {
|
||||
throw new Error('Renote target is not public or home');
|
||||
throw ErrorHandling('Renote target is not public or home');
|
||||
}
|
||||
|
||||
// Renote対象がpublicではないならhomeにする
|
||||
@@ -316,7 +317,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
if (data.visibility === 'specified') {
|
||||
if (data.visibleUsers == null) throw new Error('invalid param');
|
||||
if (data.visibleUsers == null) throw ErrorHandling('invalid param');
|
||||
|
||||
for (const u of data.visibleUsers) {
|
||||
if (!mentionedUsers.some(x => x.id === u.id)) {
|
||||
@@ -436,6 +437,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
const err = new Error('Duplicated note');
|
||||
err.name = 'duplicated';
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(err, 'stack', { value: '' });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -525,7 +529,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
// 未読通知を作成
|
||||
if (data.visibility === 'specified') {
|
||||
if (data.visibleUsers == null) throw new Error('invalid param');
|
||||
if (data.visibleUsers == null) throw ErrorHandling('invalid param');
|
||||
|
||||
for (const u of data.visibleUsers) {
|
||||
// ローカルユーザーのみ
|
||||
|
||||
@@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class PollService {
|
||||
@@ -45,16 +46,16 @@ export class PollService {
|
||||
public async vote(user: User, note: Note, choice: number) {
|
||||
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||
|
||||
if (poll == null) throw new Error('poll not found');
|
||||
if (poll == null) throw ErrorHandling('poll not found');
|
||||
|
||||
// Check whether is valid choice
|
||||
if (poll.choices[choice] == null) throw new Error('invalid choice param');
|
||||
if (poll.choices[choice] == null) throw ErrorHandling('invalid choice param');
|
||||
|
||||
// Check blocking
|
||||
if (note.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||
if (blocked) {
|
||||
throw new Error('blocked');
|
||||
throw ErrorHandling('blocked');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +67,10 @@ export class PollService {
|
||||
|
||||
if (poll.multiple) {
|
||||
if (exist.some(x => x.choice === choice)) {
|
||||
throw new Error('already voted');
|
||||
throw ErrorHandling('already voted');
|
||||
}
|
||||
} else if (exist.length !== 0) {
|
||||
throw new Error('already voted');
|
||||
throw ErrorHandling('already voted');
|
||||
}
|
||||
|
||||
// Create vote
|
||||
@@ -94,10 +95,10 @@ export class PollService {
|
||||
@bindThis
|
||||
public async deliverQuestionUpdate(noteId: Note['id']) {
|
||||
const note = await this.notesRepository.findOneBy({ id: noteId });
|
||||
if (note == null) throw new Error('note not found');
|
||||
if (note == null) throw ErrorHandling('note not found');
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: note.userId });
|
||||
if (user == null) throw new Error('note not found');
|
||||
if (user == null) throw ErrorHandling('note not found');
|
||||
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user));
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { deepClone } from '@/misc/clone.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
||||
|
||||
@@ -74,7 +75,7 @@ export class RelayService {
|
||||
});
|
||||
|
||||
if (relay == null) {
|
||||
throw new Error('relay not found');
|
||||
throw ErrorHandling('relay not found');
|
||||
}
|
||||
|
||||
const relayActor = await this.getRelayActor();
|
||||
|
||||
@@ -18,6 +18,7 @@ import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
|
||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteUserResolveService {
|
||||
@@ -47,7 +48,7 @@ export class RemoteUserResolveService {
|
||||
this.logger.info(`return local user: ${usernameLower}`);
|
||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||
if (u == null) {
|
||||
throw new Error('user not found');
|
||||
throw ErrorHandling('user not found');
|
||||
} else {
|
||||
return u;
|
||||
}
|
||||
@@ -60,7 +61,7 @@ export class RemoteUserResolveService {
|
||||
this.logger.info(`return local user: ${usernameLower}`);
|
||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||
if (u == null) {
|
||||
throw new Error('user not found');
|
||||
throw ErrorHandling('user not found');
|
||||
} else {
|
||||
return u;
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export class RemoteUserResolveService {
|
||||
.getUserFromApId(self.href)
|
||||
.then((u) => {
|
||||
if (u == null) {
|
||||
throw new Error('local user not found');
|
||||
throw ErrorHandling('local user not found');
|
||||
} else {
|
||||
return u;
|
||||
}
|
||||
@@ -112,7 +113,7 @@ export class RemoteUserResolveService {
|
||||
// validate uri
|
||||
const uri = new URL(self.href);
|
||||
if (uri.hostname !== host) {
|
||||
throw new Error('Invalid uri');
|
||||
throw ErrorHandling('Invalid uri');
|
||||
}
|
||||
|
||||
await this.usersRepository.update({
|
||||
@@ -130,7 +131,7 @@ export class RemoteUserResolveService {
|
||||
this.logger.info(`return resynced remote user: ${acctLower}`);
|
||||
return await this.usersRepository.findOneBy({ uri: self.href }).then(u => {
|
||||
if (u == null) {
|
||||
throw new Error('user not found');
|
||||
throw ErrorHandling('user not found');
|
||||
} else {
|
||||
return u as LocalUser | RemoteUser;
|
||||
}
|
||||
@@ -146,12 +147,12 @@ export class RemoteUserResolveService {
|
||||
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
||||
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
|
||||
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);
|
||||
throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`);
|
||||
throw ErrorHandling(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`);
|
||||
});
|
||||
const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self');
|
||||
if (!self) {
|
||||
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
|
||||
throw new Error('self link not found');
|
||||
throw ErrorHandling('self link not found');
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { Index, MeiliSearch } from 'meilisearch';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
type K = string;
|
||||
type V = string | number | boolean;
|
||||
@@ -37,7 +38,7 @@ function compileValue(value: V): string {
|
||||
} else if (typeof value === 'boolean') {
|
||||
return value.toString();
|
||||
}
|
||||
throw new Error('unrecognized value');
|
||||
throw ErrorHandling('unrecognized value');
|
||||
}
|
||||
|
||||
function compileQuery(q: Q): string {
|
||||
@@ -51,7 +52,7 @@ function compileQuery(q: Q): string {
|
||||
case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`;
|
||||
case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`;
|
||||
case 'not': return `(NOT ${compileQuery(q.q)})`;
|
||||
default: throw new Error('unrecognized query operator');
|
||||
default: throw ErrorHandling('unrecognized query operator');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as jsrsasign from 'jsrsasign';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const ECC_PRELUDE = Buffer.from([0x04]);
|
||||
const NULL_BYTE = Buffer.from([0]);
|
||||
@@ -72,7 +73,7 @@ function verifyCertificateChain(certificates: string[]) {
|
||||
const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1];
|
||||
|
||||
const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]);
|
||||
if (certStruct == null) throw new Error('certStruct is null');
|
||||
if (certStruct == null) throw ErrorHandling('certStruct is null');
|
||||
|
||||
const algorithm = certificate.getSignatureAlgorithmField();
|
||||
const signatureHex = certificate.getSignatureValueHex();
|
||||
@@ -142,14 +143,14 @@ export class TwoFactorAuthenticationService {
|
||||
challenge: string
|
||||
}) {
|
||||
if (clientData.type !== 'webauthn.get') {
|
||||
throw new Error('type is not webauthn.get');
|
||||
throw ErrorHandling('type is not webauthn.get');
|
||||
}
|
||||
|
||||
if (this.hash(clientData.challenge).toString('hex') !== challenge) {
|
||||
throw new Error('challenge mismatch');
|
||||
throw ErrorHandling('challenge mismatch');
|
||||
}
|
||||
if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
|
||||
throw new Error('origin mismatch');
|
||||
throw ErrorHandling('origin mismatch');
|
||||
}
|
||||
|
||||
const verificationData = Buffer.concat(
|
||||
@@ -171,11 +172,11 @@ export class TwoFactorAuthenticationService {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
throw ErrorHandling('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
throw ErrorHandling('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyU2F = Buffer.concat(
|
||||
@@ -206,7 +207,7 @@ export class TwoFactorAuthenticationService {
|
||||
credentialId: Buffer,
|
||||
}) {
|
||||
if (attStmt.alg !== -7) {
|
||||
throw new Error('alg mismatch');
|
||||
throw ErrorHandling('alg mismatch');
|
||||
}
|
||||
|
||||
const verificationData = Buffer.concat([
|
||||
@@ -219,11 +220,11 @@ export class TwoFactorAuthenticationService {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
throw ErrorHandling('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
throw ErrorHandling('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
@@ -232,7 +233,7 @@ export class TwoFactorAuthenticationService {
|
||||
);
|
||||
|
||||
if (!attCert.equals(publicKeyData)) {
|
||||
throw new Error('public key mismatch');
|
||||
throw ErrorHandling('public key mismatch');
|
||||
}
|
||||
|
||||
const isValid = crypto
|
||||
@@ -278,7 +279,7 @@ export class TwoFactorAuthenticationService {
|
||||
const signature = jwsParts[2];
|
||||
|
||||
if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) {
|
||||
throw new Error('invalid nonce');
|
||||
throw ErrorHandling('invalid nonce');
|
||||
}
|
||||
|
||||
const certificateChain = header.x5c
|
||||
@@ -286,11 +287,11 @@ export class TwoFactorAuthenticationService {
|
||||
.concat([GSR2]);
|
||||
|
||||
if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
|
||||
throw new Error('invalid common name');
|
||||
throw ErrorHandling('invalid common name');
|
||||
}
|
||||
|
||||
if (!verifyCertificateChain(certificateChain)) {
|
||||
throw new Error('Invalid certificate chain!');
|
||||
throw ErrorHandling('Invalid certificate chain!');
|
||||
}
|
||||
|
||||
const signatureBase = Buffer.from(
|
||||
@@ -306,11 +307,11 @@ export class TwoFactorAuthenticationService {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
throw ErrorHandling('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
throw ErrorHandling('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
@@ -355,11 +356,11 @@ export class TwoFactorAuthenticationService {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
throw ErrorHandling('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
throw ErrorHandling('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
@@ -373,11 +374,11 @@ export class TwoFactorAuthenticationService {
|
||||
};
|
||||
} else if (attStmt.ecdaaKeyId) {
|
||||
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
|
||||
throw new Error('ECDAA-Verify is not supported');
|
||||
throw ErrorHandling('ECDAA-Verify is not supported');
|
||||
} else {
|
||||
if (attStmt.alg !== -7) throw new Error('alg mismatch');
|
||||
if (attStmt.alg !== -7) throw ErrorHandling('alg mismatch');
|
||||
|
||||
throw new Error('self attestation is not supported');
|
||||
throw ErrorHandling('self attestation is not supported');
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -400,7 +401,7 @@ export class TwoFactorAuthenticationService {
|
||||
}) {
|
||||
const x5c: Buffer[] = attStmt.x5c;
|
||||
if (x5c.length !== 1) {
|
||||
throw new Error('x5c length does not match expectation');
|
||||
throw ErrorHandling('x5c length does not match expectation');
|
||||
}
|
||||
|
||||
const attCert = x5c[0];
|
||||
@@ -410,11 +411,11 @@ export class TwoFactorAuthenticationService {
|
||||
const negTwo: Buffer = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
throw ErrorHandling('invalid or no -2 key given');
|
||||
}
|
||||
const negThree: Buffer = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
throw ErrorHandling('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyU2F = Buffer.concat(
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import Logger from '../logger.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const logger = new Logger('following/create');
|
||||
|
||||
@@ -459,8 +460,8 @@ export class UserFollowingService implements OnModuleInit {
|
||||
this.userBlockingService.checkBlocked(followee.id, follower.id),
|
||||
]);
|
||||
|
||||
if (blocking) throw new Error('blocking');
|
||||
if (blocked) throw new Error('blocked');
|
||||
if (blocking) throw ErrorHandling('blocking');
|
||||
if (blocked) throw ErrorHandling('blocked');
|
||||
|
||||
const followRequest = await this.followRequestsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
|
||||
@@ -30,6 +30,7 @@ import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { LdSignatureService } from './LdSignatureService.js';
|
||||
import { ApMfmService } from './ApMfmService.js';
|
||||
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApRendererService {
|
||||
@@ -98,7 +99,7 @@ export class ApRendererService {
|
||||
to = [`${attributedTo}/followers`];
|
||||
cc = [];
|
||||
} else {
|
||||
throw new Error('renderAnnounce: cannot render non-public note');
|
||||
throw ErrorHandling('renderAnnounce: cannot render non-public note');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -120,7 +121,7 @@ export class ApRendererService {
|
||||
@bindThis
|
||||
public renderBlock(block: Blocking): IBlock {
|
||||
if (block.blockee?.uri == null) {
|
||||
throw new Error('renderBlock: missing blockee uri');
|
||||
throw ErrorHandling('renderBlock: missing blockee uri');
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
import { ApRendererService } from './ApRendererService.js';
|
||||
import { ApRequestService } from './ApRequestService.js';
|
||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export class Resolver {
|
||||
private history: Set<string>;
|
||||
@@ -60,7 +61,7 @@ export class Resolver {
|
||||
if (isCollectionOrOrderedCollection(collection)) {
|
||||
return collection;
|
||||
} else {
|
||||
throw new Error(`unrecognized collection type: ${collection.type}`);
|
||||
throw ErrorHandling(`unrecognized collection type: ${collection.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,15 +75,15 @@ export class Resolver {
|
||||
// URLs with fragment parts cannot be resolved correctly because
|
||||
// the fragment part does not get transmitted over HTTP(S).
|
||||
// Avoid strange behaviour by not trying to resolve these at all.
|
||||
throw new Error(`cannot resolve URL with fragment: ${value}`);
|
||||
throw ErrorHandling(`cannot resolve URL with fragment: ${value}`);
|
||||
}
|
||||
|
||||
if (this.history.has(value)) {
|
||||
throw new Error('cannot resolve already resolved one');
|
||||
throw ErrorHandling('cannot resolve already resolved one');
|
||||
}
|
||||
|
||||
if (this.history.size > this.recursionLimit) {
|
||||
throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
||||
throw ErrorHandling(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
||||
}
|
||||
|
||||
this.history.add(value);
|
||||
@@ -94,7 +95,7 @@ export class Resolver {
|
||||
|
||||
const meta = await this.metaService.fetch();
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
||||
throw new Error('Instance is blocked');
|
||||
throw ErrorHandling('Instance is blocked');
|
||||
}
|
||||
|
||||
if (this.config.signToActivityPubGet && !this.user) {
|
||||
@@ -110,7 +111,7 @@ export class Resolver {
|
||||
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
||||
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
||||
) {
|
||||
throw new Error('invalid response');
|
||||
throw ErrorHandling('invalid response');
|
||||
}
|
||||
|
||||
return object;
|
||||
@@ -119,7 +120,7 @@ export class Resolver {
|
||||
@bindThis
|
||||
private resolveLocal(url: string): Promise<IObject> {
|
||||
const parsed = this.apDbResolverService.parseUri(url);
|
||||
if (!parsed.local) throw new Error('resolveLocal: not local');
|
||||
if (!parsed.local) throw ErrorHandling('resolveLocal: not local');
|
||||
|
||||
switch (parsed.type) {
|
||||
case 'notes':
|
||||
@@ -147,14 +148,14 @@ export class Resolver {
|
||||
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
|
||||
case 'follows':
|
||||
// rest should be <followee id>
|
||||
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
|
||||
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw ErrorHandling('resolveLocal: invalid follow URI');
|
||||
|
||||
return Promise.all(
|
||||
[parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
|
||||
)
|
||||
.then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url)));
|
||||
default:
|
||||
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
|
||||
throw ErrorHandling(`resolveLocal: type ${parsed.type} unhandled`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { ApQuestionService } from './ApQuestionService.js';
|
||||
import { ApImageService } from './ApImageService.js';
|
||||
import type { Resolver } from '../ApResolverService.js';
|
||||
import type { IObject, IPost } from '../type.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApNoteService {
|
||||
@@ -80,16 +81,28 @@ export class ApNoteService {
|
||||
const expectHost = this.utilityService.extractDbHost(uri);
|
||||
|
||||
if (!validPost.includes(getApType(object))) {
|
||||
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
||||
const error = new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(error, 'stack', { value: '' });
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
|
||||
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
|
||||
const error = new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(error, 'stack', { value: '' });
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
|
||||
if (object.attributedTo && actualHost !== expectHost) {
|
||||
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
const error = new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(error, 'stack', { value: '' });
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -123,7 +136,7 @@ export class ApNoteService {
|
||||
value,
|
||||
object,
|
||||
});
|
||||
throw new Error('invalid note');
|
||||
throw ErrorHandling('invalid note');
|
||||
}
|
||||
|
||||
const note = object as IPost;
|
||||
@@ -131,27 +144,27 @@ export class ApNoteService {
|
||||
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
||||
|
||||
if (note.id && !checkHttps(note.id)) {
|
||||
throw new Error('unexpected schema of note.id: ' + note.id);
|
||||
throw ErrorHandling('unexpected schema of note.id: ' + note.id);
|
||||
}
|
||||
|
||||
const url = getOneApHrefNullable(note.url);
|
||||
|
||||
if (url && !checkHttps(url)) {
|
||||
throw new Error('unexpected schema of note url: ' + url);
|
||||
throw ErrorHandling('unexpected schema of note url: ' + url);
|
||||
}
|
||||
|
||||
this.logger.info(`Creating the Note: ${note.id}`);
|
||||
|
||||
// 投稿者をフェッチ
|
||||
if (note.attributedTo == null) {
|
||||
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
|
||||
throw ErrorHandling('invalid note.attributedTo: ' + note.attributedTo);
|
||||
}
|
||||
|
||||
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as RemoteUser;
|
||||
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
throw ErrorHandling('actor has been suspended');
|
||||
}
|
||||
|
||||
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
|
||||
@@ -186,7 +199,7 @@ export class ApNoteService {
|
||||
.then(x => {
|
||||
if (x == null) {
|
||||
this.logger.warn('Specified inReplyTo, but not found');
|
||||
throw new Error('inReplyTo not found');
|
||||
throw ErrorHandling('inReplyTo not found');
|
||||
}
|
||||
|
||||
return x;
|
||||
@@ -223,7 +236,7 @@ export class ApNoteService {
|
||||
quote = results.filter((x): x is { status: 'ok', res: Note } => x.status === 'ok').map(x => x.res).at(0);
|
||||
if (!quote) {
|
||||
if (results.some(x => x.status === 'temperror')) {
|
||||
throw new Error('quote resolve failed');
|
||||
throw ErrorHandling('quote resolve failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,7 +310,7 @@ export class ApNoteService {
|
||||
this.logger.info('The note is already inserted while creating itself, reading again');
|
||||
const duplicate = await this.fetchNote(value);
|
||||
if (!duplicate) {
|
||||
throw new Error('The note creation failed with duplication error even when there is no duplication');
|
||||
throw ErrorHandling('The note creation failed with duplication error even when there is no duplication');
|
||||
}
|
||||
return duplicate;
|
||||
}
|
||||
@@ -376,7 +389,7 @@ export class ApNoteService {
|
||||
});
|
||||
|
||||
const emoji = await this.emojisRepository.findOneBy({ host, name });
|
||||
if (emoji == null) throw new Error('emoji update failed');
|
||||
if (emoji == null) throw ErrorHandling('emoji update failed');
|
||||
return emoji;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: tamaina and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Never use `./error.js` because jest can't use it.
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: MomentQYC and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
export function ErrorHandling(message: string, reply?: FastifyReply, statusCode?: number): Error {
|
||||
const error = new Error(message);
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
error.stack = undefined;
|
||||
}
|
||||
if (reply) {
|
||||
reply.code(statusCode ?? 500);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
export function ErrorHandler(error: Error, request: FastifyRequest, reply: FastifyReply): void {
|
||||
throw ErrorHandling(error.message, reply);
|
||||
}
|
||||
@@ -12,5 +12,9 @@ export class FastifyReplyError extends Error {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.statusCode = statusCode;
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
Object.defineProperty(this, 'stack', { value: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ErrorHandler } from '@/misc/error.js';
|
||||
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||
import { ApiServerService } from './api/ApiServerService.js';
|
||||
@@ -76,6 +77,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
||||
});
|
||||
this.#fastify = fastify;
|
||||
fastify.setErrorHandler(ErrorHandler);
|
||||
|
||||
// HSTS
|
||||
// 6months (15552000sec)
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class GetterService {
|
||||
@@ -61,7 +62,7 @@ export class GetterService {
|
||||
const user = await this.getUser(userId);
|
||||
|
||||
if (!this.userEntityService.isRemoteUser(user)) {
|
||||
throw new Error('user is not a remote user');
|
||||
throw ErrorHandling('user is not a remote user');
|
||||
}
|
||||
|
||||
return user;
|
||||
@@ -75,7 +76,7 @@ export class GetterService {
|
||||
const user = await this.getUser(userId);
|
||||
|
||||
if (!this.userEntityService.isLocalUser(user)) {
|
||||
throw new Error('user is not a local user');
|
||||
throw ErrorHandling('user is not a local user');
|
||||
}
|
||||
|
||||
return user;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -67,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
try {
|
||||
if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only');
|
||||
if (new URL(ps.inbox).protocol !== 'https:') throw ErrorHandling('https only');
|
||||
} catch {
|
||||
throw new ApiError(meta.errors.invalidUrl);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -53,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
]);
|
||||
|
||||
if (user == null || profile == null) {
|
||||
throw new Error('user not found');
|
||||
throw ErrorHandling('user not found');
|
||||
}
|
||||
|
||||
const isModerator = await this.roleService.isModerator(user);
|
||||
@@ -61,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
const _me = await this.usersRepository.findOneByOrFail({ id: me.id });
|
||||
if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) {
|
||||
throw new Error('cannot show info of admin');
|
||||
throw ErrorHandling('cannot show info of admin');
|
||||
}
|
||||
|
||||
const signins = await this.signinsRepository.findBy({ userId: user.id });
|
||||
|
||||
@@ -12,6 +12,7 @@ import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['antennas'],
|
||||
@@ -87,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if ((ps.keywords.length === 0) || ps.keywords[0].every(x => x === '')) {
|
||||
throw new Error('invalid param');
|
||||
throw ErrorHandling('invalid param');
|
||||
}
|
||||
|
||||
const currentAntennasCount = await this.antennasRepository.countBy({
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
@@ -41,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||
|
||||
if (profile.twoFactorTempSecret == null) {
|
||||
throw new Error('二段階認証の設定が開始されていません');
|
||||
throw ErrorHandling('二段階認証の設定が開始されていません');
|
||||
}
|
||||
|
||||
const delta = OTPAuth.TOTP.validate({
|
||||
@@ -52,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
});
|
||||
|
||||
if (delta === null) {
|
||||
throw new Error('not verified');
|
||||
throw ErrorHandling('not verified');
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
|
||||
import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
|
||||
|
||||
@@ -64,20 +65,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw ErrorHandling('incorrect password');
|
||||
}
|
||||
|
||||
if (!profile.twoFactorEnabled) {
|
||||
throw new Error('2fa not enabled');
|
||||
throw ErrorHandling('2fa not enabled');
|
||||
}
|
||||
|
||||
const clientData = JSON.parse(ps.clientDataJSON);
|
||||
|
||||
if (clientData.type !== 'webauthn.create') {
|
||||
throw new Error('not a creation attestation');
|
||||
throw ErrorHandling('not a creation attestation');
|
||||
}
|
||||
if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
|
||||
throw new Error('origin mismatch');
|
||||
throw ErrorHandling('origin mismatch');
|
||||
}
|
||||
|
||||
const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
|
||||
@@ -86,14 +87,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
const rpIdHash = attestation.authData.slice(0, 32);
|
||||
if (!rpIdHashReal.equals(rpIdHash)) {
|
||||
throw new Error('rpIdHash mismatch');
|
||||
throw ErrorHandling('rpIdHash mismatch');
|
||||
}
|
||||
|
||||
const flags = attestation.authData[32];
|
||||
|
||||
// eslint:disable-next-line:no-bitwise
|
||||
if (!(flags & 1)) {
|
||||
throw new Error('user not present');
|
||||
throw ErrorHandling('user not present');
|
||||
}
|
||||
|
||||
const authData = Buffer.from(attestation.authData);
|
||||
@@ -102,13 +103,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const publicKeyData = authData.slice(55 + credentialIdLength);
|
||||
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
|
||||
if (publicKey.get(3) !== -7) {
|
||||
throw new Error('alg mismatch');
|
||||
throw ErrorHandling('alg mismatch');
|
||||
}
|
||||
|
||||
const procedures = this.twoFactorAuthenticationService.getProcedures();
|
||||
|
||||
if (!(procedures as any)[attestation.fmt]) {
|
||||
throw new Error(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`);
|
||||
throw ErrorHandling(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`);
|
||||
}
|
||||
|
||||
const verificationData = (procedures as any)[attestation.fmt].verify({
|
||||
@@ -119,7 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
publicKey,
|
||||
rpIdHash,
|
||||
});
|
||||
if (!verificationData.valid) throw new Error('signature invalid');
|
||||
if (!verificationData.valid) throw ErrorHandling('signature invalid');
|
||||
|
||||
const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
|
||||
userId: me.id,
|
||||
@@ -129,7 +130,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
});
|
||||
|
||||
if (!attestationChallenge) {
|
||||
throw new Error('non-existent challenge');
|
||||
throw ErrorHandling('non-existent challenge');
|
||||
}
|
||||
|
||||
await this.attestationChallengesRepository.delete({
|
||||
@@ -142,7 +143,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
new Date().getTime() - attestationChallenge.createdAt.getTime() >=
|
||||
5 * 60 * 1000
|
||||
) {
|
||||
throw new Error('expired challenge');
|
||||
throw ErrorHandling('expired challenge');
|
||||
}
|
||||
|
||||
const credentialIdString = credentialId.toString('hex');
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
@@ -43,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw ErrorHandling('incorrect password');
|
||||
}
|
||||
|
||||
// Generate user's secret key
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
@@ -46,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw ErrorHandling('incorrect password');
|
||||
}
|
||||
|
||||
// Make sure we only delete the user's own creds
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
@@ -42,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw ErrorHandling('incorrect password');
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
@@ -47,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw ErrorHandling('incorrect password');
|
||||
}
|
||||
|
||||
await this.deleteAccountService.deleteAccount(me);
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { resetDb } from '@/misc/reset-db.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['non-productive'],
|
||||
@@ -39,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private redisClient: Redis.Redis,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
||||
if (process.env.NODE_ENV !== 'test') throw ErrorHandling('NODE_ENV is not a test');
|
||||
|
||||
await redisClient.flushdb();
|
||||
await resetDb(this.db);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ErrorHandling } from '@/misc/error.js'; // TODO Line 51
|
||||
|
||||
export const meta = {
|
||||
tags: ['reset password'],
|
||||
|
||||
@@ -19,6 +19,7 @@ import { AntennaChannelService } from './channels/antenna.js';
|
||||
import { DriveChannelService } from './channels/drive.js';
|
||||
import { HashtagChannelService } from './channels/hashtag.js';
|
||||
import { RoleTimelineChannelService } from './channels/role-timeline.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsService {
|
||||
@@ -59,7 +60,7 @@ export class ChannelsService {
|
||||
case 'admin': return this.adminChannelService;
|
||||
|
||||
default:
|
||||
throw new Error(`no such channel: ${name}`);
|
||||
throw ErrorHandling(`no such channel: ${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { MemoryKVCache } from '@/misc/cache.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import Logger from '@/logger.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
import type { ServerResponse } from 'node:http';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
|
||||
@@ -353,6 +354,7 @@ export class OAuth2ProviderService {
|
||||
public async createServer(fastify: FastifyInstance): Promise<void> {
|
||||
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
||||
// https://indieauth.spec.indieweb.org/#indieauth-server-metadata
|
||||
|
||||
fastify.get('/.well-known/oauth-authorization-server', async (_request, reply) => {
|
||||
reply.send({
|
||||
issuer: this.config.url,
|
||||
@@ -370,7 +372,7 @@ export class OAuth2ProviderService {
|
||||
fastify.get('/oauth/authorize', async (request, reply) => {
|
||||
const oauth2 = (request.raw as MiddlewareRequest).oauth2;
|
||||
if (!oauth2) {
|
||||
throw new Error('Unexpected lack of authorization information');
|
||||
throw ErrorHandling('Unexpected lack of authorization information');
|
||||
}
|
||||
|
||||
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
|
||||
|
||||
@@ -146,18 +146,18 @@ export class ClientServerService {
|
||||
if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) {
|
||||
const token = request.cookies.token;
|
||||
if (token == null) {
|
||||
reply.code(401);
|
||||
throw new Error('login required');
|
||||
reply.code(401).send('Login required');
|
||||
return;
|
||||
}
|
||||
const user = await this.usersRepository.findOneBy({ token });
|
||||
if (user == null) {
|
||||
reply.code(403);
|
||||
throw new Error('no such user');
|
||||
reply.code(403).send('No such user');
|
||||
return;
|
||||
}
|
||||
const isAdministrator = await this.roleService.isAdministrator(user);
|
||||
if (!isAdministrator) {
|
||||
reply.code(403);
|
||||
throw new Error('access denied');
|
||||
reply.code(403).send('Access denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -682,12 +682,13 @@ export class ClientServerService {
|
||||
|
||||
fastify.setErrorHandler(async (error, request, reply) => {
|
||||
const errId = randomUUID();
|
||||
const stack = (process.env.NODE_ENV === 'production') ? '' : error.stack;
|
||||
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routerPath}: ${error.message}`, {
|
||||
path: request.routerPath,
|
||||
params: request.params,
|
||||
query: request.query,
|
||||
code: error.name,
|
||||
stack: error.stack,
|
||||
stack,
|
||||
id: errId,
|
||||
});
|
||||
reply.code(500);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { ErrorHandling } from '@/misc/error.js';
|
||||
|
||||
@Injectable()
|
||||
export class UrlPreviewService {
|
||||
@@ -84,11 +85,11 @@ export class UrlPreviewService {
|
||||
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
||||
|
||||
if (!(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
|
||||
throw new Error('unsupported schema included');
|
||||
throw ErrorHandling('unsupported schema included');
|
||||
}
|
||||
|
||||
if (summary.player.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) {
|
||||
throw new Error('unsupported schema included');
|
||||
throw ErrorHandling('unsupported schema included');
|
||||
}
|
||||
|
||||
summary.icon = this.wrap(summary.icon);
|
||||
|
||||
Reference in New Issue
Block a user