feat: Hiding stack traces in production env

This commit is contained in:
MomentQYC 2023-08-10 17:21:56 +08:00
parent 827616f630
commit 191c5f07a4
45 changed files with 217 additions and 140 deletions

View File

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { ErrorHandling } from '@/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}`);
}
}
}

View File

@ -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 '@/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(),

View File

@ -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 '@/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(),

View File

@ -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 '@/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;
}
}
}

View File

@ -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 '@/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');
}
}
}

View File

@ -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 '@/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) {
// ローカルユーザーのみ

View File

@ -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 '@/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));

View File

@ -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 '@/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();

View File

@ -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 '@/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;
}

View File

@ -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 '@/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');
}
}

View File

@ -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 '@/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(

View File

@ -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 '@/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(),

View File

@ -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 '@/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 {

View File

@ -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 '@/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`);
}
}
}

View File

@ -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 '@/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;
}

View File

@ -0,0 +1,7 @@
export function ErrorHandling(message: string): Error {
const error = new Error(message);
if (process.env.NODE_ENV === "production") {
error.stack = undefined;
}
return error;
}

View File

@ -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 '@/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;

View File

@ -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 '@/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);
}

View File

@ -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 '@/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 });

View File

@ -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 '@/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({

View File

@ -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 '@/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, {

View File

@ -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 '@/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');

View File

@ -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 '@/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

View File

@ -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 '@/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

View File

@ -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 '@/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, {

View File

@ -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 '@/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);

View File

@ -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 '@/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);

View File

@ -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 '@/error.js'; // TODO Line 51
export const meta = {
tags: ['reset password'],

View File

@ -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 '@/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}`);
}
}
}

View File

@ -33,6 +33,7 @@ import Logger from '@/logger.js';
import { StatusError } from '@/misc/status-error.js';
import type { ServerResponse } from 'node:http';
import type { FastifyInstance } from 'fastify';
import { ErrorHandling } from '@/error.js';
// TODO: Consider migrating to @node-oauth/oauth2-server once
// https://github.com/node-oauth/node-oauth2-server/issues/180 is figured out.
@ -370,7 +371,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}"`);

View File

@ -42,6 +42,7 @@ import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
import { ErrorHandling } from '@/error.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -147,17 +148,17 @@ export class ClientServerService {
const token = request.cookies.token;
if (token == null) {
reply.code(401);
throw new Error('login required');
throw ErrorHandling('login required');
}
const user = await this.usersRepository.findOneBy({ token });
if (user == null) {
reply.code(403);
throw new Error('no such user');
throw ErrorHandling('no such user');
}
const isAdministrator = await this.roleService.isAdministrator(user);
if (!isAdministrator) {
reply.code(403);
throw new Error('access denied');
throw ErrorHandling('access denied');
}
}
});

View File

@ -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 '@/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);

View File

@ -85,6 +85,7 @@ import { deviceKind } from '@/scripts/device-kind';
import MkButton from '@/components/MkButton.vue';
import { versatileLang } from '@/scripts/intl-const';
import { defaultStore } from '@/store';
import { ErrorHandling } from '@/error';
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
@ -124,7 +125,7 @@ let tweetHeight = $ref(150);
let unknownUrl = $ref(false);
const requestUrl = new URL(props.url);
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw ErrorHandling('invalid url');
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);

View File

@ -27,13 +27,14 @@ SPDX-License-Identifier: AGPL-3.0-only
import MkWindow from '@/components/MkWindow.vue';
import { versatileLang } from '@/scripts/intl-const';
import { defaultStore } from '@/store';
import { ErrorHandling } from '@/error';
const props = defineProps<{
url: string;
}>();
const requestUrl = new URL(props.url);
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw ErrorHandling('invalid url');
let fetching = $ref(true);
let title = $ref<string | null>(null);

View File

@ -30,6 +30,7 @@ import { url as local } from '@/config';
import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip';
import { safeURIDecode } from '@/scripts/safe-uri-decode';
import { ErrorHandling } from '@/error';
const props = defineProps<{
url: string;
@ -38,7 +39,7 @@ const props = defineProps<{
const self = props.url.startsWith(local);
const url = new URL(props.url);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
if (!['http:', 'https:'].includes(url.protocol)) throw ErrorHandling('invalid url');
const el = ref();
useTooltip(el, (showing) => {

View File

@ -0,0 +1,7 @@
export function ErrorHandling(message: string): Error {
const error = new Error(message);
if (process.env.NODE_ENV === "production") {
error.stack = undefined;
}
return error;
}

View File

@ -50,6 +50,7 @@ import * as os from '@/os';
import { $i, login } from '@/account';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n';
import { ErrorHandling } from '@/error';
const props = defineProps<{
token: string;
@ -62,7 +63,7 @@ function accepted() {
state = 'accepted';
if (session && session.app.callbackUrl) {
const url = new URL(session.app.callbackUrl);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw ErrorHandling('invalid url');
location.href = `${session.app.callbackUrl}?token=${session.token}`;
}
}

View File

@ -14,6 +14,7 @@ import * as Acct from 'misskey-js/built/acct';
import * as os from '@/os';
import { mainRouter } from '@/router';
import { i18n } from '@/i18n';
import { ErrorHandling } from '@/error';
async function follow(user): Promise<void> {
const { canceled } = await os.confirm({
@ -33,7 +34,7 @@ async function follow(user): Promise<void> {
const acct = new URL(location.href).searchParams.get('acct');
if (acct == null) {
throw new Error('acct required');
throw ErrorHandling('acct required');
}
let promise;

View File

@ -136,6 +136,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination from '@/components/MkPagination.vue';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
import { dateString } from '@/filters/date';
import { ErrorHandling } from '@/error';
const props = defineProps<{
host: string;
@ -173,8 +174,8 @@ async function fetch(): Promise<void> {
}
async function toggleBlock(): Promise<void> {
if (!meta) throw new Error('No meta?');
if (!instance) throw new Error('No instance?');
if (!meta) throw ErrorHandling('No meta?');
if (!instance) throw ErrorHandling('No instance?');
const { host } = instance;
await os.api('admin/update-meta', {
blockedHosts: isBlocked ? meta.blockedHosts.concat([host]) : meta.blockedHosts.filter(x => x !== host),
@ -182,7 +183,7 @@ async function toggleBlock(): Promise<void> {
}
async function toggleSuspend(): Promise<void> {
if (!instance) throw new Error('No instance?');
if (!instance) throw ErrorHandling('No instance?');
await os.api('admin/federation/update-instance', {
host: instance.host,
isSuspended: suspended,
@ -190,7 +191,7 @@ async function toggleSuspend(): Promise<void> {
}
function refreshMetadata(): void {
if (!instance) throw new Error('No instance?');
if (!instance) throw ErrorHandling('No instance?');
os.api('admin/federation/refresh-remote-instance-metadata', {
host: instance.host,
});

View File

@ -50,6 +50,7 @@ import * as os from '@/os';
import { $i, login } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { ErrorHandling } from '@/error';
const props = defineProps<{
session: string;
@ -75,7 +76,7 @@ async function accept(): Promise<void> {
state = 'accepted';
if (props.callback) {
const cbUrl = new URL(props.callback);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw ErrorHandling('invalid url');
cbUrl.searchParams.set('session', props.session);
location.href = cbUrl.href;
}

View File

@ -185,6 +185,7 @@ import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { ErrorHandling } from '@/error';
const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize'));
@ -276,7 +277,7 @@ function downloadEmojiIndex(lang: string) {
function download() {
switch (lang) {
case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
default: throw new Error('unrecognized lang: ' + lang);
default: throw ErrorHandling('unrecognized lang: ' + lang);
}
}
currentIndexes[lang] = await download();

View File

@ -51,6 +51,8 @@ import { i18n } from '@/i18n';
import { version, host } from '@/config';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { ErrorHandling } from '@/error';
const { t, ts } = i18n;
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
@ -133,27 +135,27 @@ function isObject(value: unknown): value is Record<string, unknown> {
}
function validate(profile: any): void {
if (!isObject(profile)) throw new Error('not an object');
if (!isObject(profile)) throw ErrorHandling('not an object');
// Check if unnecessary properties exist
if (Object.keys(profile).some(key => !profileProps.includes(key))) throw new Error('Unnecessary properties exist');
if (Object.keys(profile).some(key => !profileProps.includes(key))) throw ErrorHandling('Unnecessary properties exist');
if (!profile.name) throw new Error('Missing required prop: name');
if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion');
if (!profile.name) throw ErrorHandling('Missing required prop: name');
if (!profile.misskeyVersion) throw ErrorHandling('Missing required prop: misskeyVersion');
// Check if createdAt and updatedAt is Date
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw new Error('createdAt is falsy or not Date');
if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw ErrorHandling('createdAt is falsy or not Date');
if (profile.updatedAt) {
if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) {
throw new Error('updatedAt is not Date');
throw ErrorHandling('updatedAt is not Date');
}
} else if (profile.updatedAt !== null) {
throw new Error('updatedAt is not null');
throw ErrorHandling('updatedAt is not null');
}
if (!profile.settings) throw new Error('Missing required prop: settings');
if (!isObject(profile.settings)) throw new Error('Invalid prop: settings');
if (!profile.settings) throw ErrorHandling('Missing required prop: settings');
if (!isObject(profile.settings)) throw ErrorHandling('Invalid prop: settings');
}
function getSettings(): Profile['settings'] {

View File

@ -22,5 +22,5 @@ export function pleaseLogin(path?: string) {
},
}, 'closed');
throw new Error('signin required');
console.log('signin required');
}

View File

@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ErrorHandling } from '@/error';
const dateTimeIntervals = {
'day': 86400000,
'hour': 3600000,
@ -19,7 +21,7 @@ export function dateUTC(time: number[]): Date {
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
: null;
if (!d) throw new Error('wrong number of arguments');
if (!d) throw ErrorHandling('wrong number of arguments');
return new Date(d);
}

View File

@ -12,6 +12,7 @@ import { apiUrl } from '@/config';
import { $i } from '@/account';
import { alert } from '@/os';
import { i18n } from '@/i18n';
import { ErrorHandling } from '@/error';
type Uploading = {
id: string;
@ -34,7 +35,7 @@ export function uploadFile(
name?: string,
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
): Promise<Misskey.entities.DriveFile> {
if ($i == null) throw new Error('Not logged in');
if ($i == null) console.log('Not logged in');
if (folder && typeof folder === 'object') folder = folder.id;