feat: Hiding stack traces in production env
This commit is contained in:
parent
827616f630
commit
191c5f07a4
|
|
@ -6,6 +6,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
type CaptchaResponse = {
|
type CaptchaResponse = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -35,7 +36,7 @@ export class CaptchaService {
|
||||||
}, { throwErrorWhenResponseNotOk: false });
|
}, { throwErrorWhenResponseNotOk: false });
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`${res.status}`);
|
throw ErrorHandling(`${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await res.json() as CaptchaResponse;
|
return await res.json() as CaptchaResponse;
|
||||||
|
|
@ -44,48 +45,48 @@ export class CaptchaService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||||
throw new Error(`recaptcha-failed: ${errorCodes}`);
|
throw ErrorHandling(`recaptcha-failed: ${errorCodes}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||||
throw new Error(`hcaptcha-failed: ${errorCodes}`);
|
throw ErrorHandling(`hcaptcha-failed: ${errorCodes}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
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 { DI } from '@/di-symbols.js';
|
||||||
import generateNativeUserToken from '@/misc/generate-native-user-token.js';
|
import generateNativeUserToken from '@/misc/generate-native-user-token.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateSystemUserService {
|
export class CreateSystemUserService {
|
||||||
|
|
@ -49,7 +50,7 @@ export class CreateSystemUserService {
|
||||||
host: IsNull(),
|
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, {
|
account = await transactionalEntityManager.insert(User, {
|
||||||
id: this.idService.genId(),
|
id: this.idService.genId(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { query } from '@/misc/prelude/url.js';
|
import { query } from '@/misc/prelude/url.js';
|
||||||
import type { Serialized } from '@/server/api/stream/types.js';
|
import type { Serialized } from '@/server/api/stream/types.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||||
|
|
||||||
|
|
@ -107,7 +108,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
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, {
|
await this.emojisRepository.update(emoji.id, {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { correctFilename } from '@/misc/correct-filename.js';
|
import { correctFilename } from '@/misc/correct-filename.js';
|
||||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
type AddFileArgs = {
|
type AddFileArgs = {
|
||||||
/** User who wish to add file */
|
/** User who wish to add file */
|
||||||
|
|
@ -535,7 +536,7 @@ export class DriveService {
|
||||||
userId: user ? user.id : IsNull(),
|
userId: user ? user.id : IsNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (driveFolder == null) throw new Error('folder-not-found');
|
if (driveFolder == null) throw ErrorHandling('folder-not-found');
|
||||||
|
|
||||||
return driveFolder;
|
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);
|
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Failed to delete the file from the object storage with the given key: ${key}`, {
|
const error = new Error(`Failed to delete the file from the object storage with the given key: ${key}`);
|
||||||
cause: err,
|
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 { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { parseUlid } from '@/misc/id/ulid.js';
|
import { parseUlid } from '@/misc/id/ulid.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IdService {
|
export class IdService {
|
||||||
|
|
@ -35,7 +36,7 @@ export class IdService {
|
||||||
case 'meidg': return genMeidg(date);
|
case 'meidg': return genMeidg(date);
|
||||||
case 'ulid': return ulid(date.getTime());
|
case 'ulid': return ulid(date.getTime());
|
||||||
case 'objectid': return genObjectId(date);
|
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 'meid': return parseMeid(id);
|
||||||
case 'meidg': return parseMeidg(id);
|
case 'meidg': return parseMeidg(id);
|
||||||
case 'ulid': return parseUlid(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 { RoleService } from '@/core/RoleService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { SearchService } from '@/core/SearchService.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);
|
const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||||
|
|
||||||
|
|
@ -251,7 +252,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
|
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
|
||||||
if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) {
|
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にする
|
// Renote対象がpublicではないならhomeにする
|
||||||
|
|
@ -316,7 +317,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.visibility === 'specified') {
|
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) {
|
for (const u of data.visibleUsers) {
|
||||||
if (!mentionedUsers.some(x => x.id === u.id)) {
|
if (!mentionedUsers.some(x => x.id === u.id)) {
|
||||||
|
|
@ -436,6 +437,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (isDuplicateKeyValueError(e)) {
|
if (isDuplicateKeyValueError(e)) {
|
||||||
const err = new Error('Duplicated note');
|
const err = new Error('Duplicated note');
|
||||||
err.name = 'duplicated';
|
err.name = 'duplicated';
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
Object.defineProperty(err, 'stack', { value: ''});
|
||||||
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,7 +529,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// 未読通知を作成
|
// 未読通知を作成
|
||||||
if (data.visibility === 'specified') {
|
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) {
|
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 { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PollService {
|
export class PollService {
|
||||||
|
|
@ -45,16 +46,16 @@ export class PollService {
|
||||||
public async vote(user: User, note: Note, choice: number) {
|
public async vote(user: User, note: Note, choice: number) {
|
||||||
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
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
|
// 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
|
// Check blocking
|
||||||
if (note.userId !== user.id) {
|
if (note.userId !== user.id) {
|
||||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||||
if (blocked) {
|
if (blocked) {
|
||||||
throw new Error('blocked');
|
throw ErrorHandling('blocked');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,10 +67,10 @@ export class PollService {
|
||||||
|
|
||||||
if (poll.multiple) {
|
if (poll.multiple) {
|
||||||
if (exist.some(x => x.choice === choice)) {
|
if (exist.some(x => x.choice === choice)) {
|
||||||
throw new Error('already voted');
|
throw ErrorHandling('already voted');
|
||||||
}
|
}
|
||||||
} else if (exist.length !== 0) {
|
} else if (exist.length !== 0) {
|
||||||
throw new Error('already voted');
|
throw ErrorHandling('already voted');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create vote
|
// Create vote
|
||||||
|
|
@ -94,10 +95,10 @@ export class PollService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deliverQuestionUpdate(noteId: Note['id']) {
|
public async deliverQuestionUpdate(noteId: Note['id']) {
|
||||||
const note = await this.notesRepository.findOneBy({ id: noteId });
|
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 });
|
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)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), 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 { DI } from '@/di-symbols.js';
|
||||||
import { deepClone } from '@/misc/clone.js';
|
import { deepClone } from '@/misc/clone.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
const ACTOR_USERNAME = 'relay.actor' as const;
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ export class RelayService {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (relay == null) {
|
if (relay == null) {
|
||||||
throw new Error('relay not found');
|
throw ErrorHandling('relay not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.getRelayActor();
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
|
||||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RemoteUserResolveService {
|
export class RemoteUserResolveService {
|
||||||
|
|
@ -47,7 +48,7 @@ export class RemoteUserResolveService {
|
||||||
this.logger.info(`return local user: ${usernameLower}`);
|
this.logger.info(`return local user: ${usernameLower}`);
|
||||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw ErrorHandling('user not found');
|
||||||
} else {
|
} else {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +61,7 @@ export class RemoteUserResolveService {
|
||||||
this.logger.info(`return local user: ${usernameLower}`);
|
this.logger.info(`return local user: ${usernameLower}`);
|
||||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw ErrorHandling('user not found');
|
||||||
} else {
|
} else {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +83,7 @@ export class RemoteUserResolveService {
|
||||||
.getUserFromApId(self.href)
|
.getUserFromApId(self.href)
|
||||||
.then((u) => {
|
.then((u) => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('local user not found');
|
throw ErrorHandling('local user not found');
|
||||||
} else {
|
} else {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +113,7 @@ export class RemoteUserResolveService {
|
||||||
// validate uri
|
// validate uri
|
||||||
const uri = new URL(self.href);
|
const uri = new URL(self.href);
|
||||||
if (uri.hostname !== host) {
|
if (uri.hostname !== host) {
|
||||||
throw new Error('Invalid uri');
|
throw ErrorHandling('Invalid uri');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.usersRepository.update({
|
await this.usersRepository.update({
|
||||||
|
|
@ -130,7 +131,7 @@ export class RemoteUserResolveService {
|
||||||
this.logger.info(`return resynced remote user: ${acctLower}`);
|
this.logger.info(`return resynced remote user: ${acctLower}`);
|
||||||
return await this.usersRepository.findOneBy({ uri: self.href }).then(u => {
|
return await this.usersRepository.findOneBy({ uri: self.href }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw ErrorHandling('user not found');
|
||||||
} else {
|
} else {
|
||||||
return u as LocalUser | RemoteUser;
|
return u as LocalUser | RemoteUser;
|
||||||
}
|
}
|
||||||
|
|
@ -146,12 +147,12 @@ export class RemoteUserResolveService {
|
||||||
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
||||||
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
|
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
|
||||||
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);
|
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');
|
const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self');
|
||||||
if (!self) {
|
if (!self) {
|
||||||
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
|
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;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { Index, MeiliSearch } from 'meilisearch';
|
import type { Index, MeiliSearch } from 'meilisearch';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
type K = string;
|
type K = string;
|
||||||
type V = string | number | boolean;
|
type V = string | number | boolean;
|
||||||
|
|
@ -37,7 +38,7 @@ function compileValue(value: V): string {
|
||||||
} else if (typeof value === 'boolean') {
|
} else if (typeof value === 'boolean') {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
throw new Error('unrecognized value');
|
throw ErrorHandling('unrecognized value');
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileQuery(q: Q): string {
|
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 '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 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`;
|
||||||
case 'not': return `(NOT ${compileQuery(q.q)})`;
|
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 { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const ECC_PRELUDE = Buffer.from([0x04]);
|
const ECC_PRELUDE = Buffer.from([0x04]);
|
||||||
const NULL_BYTE = Buffer.from([0]);
|
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 CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1];
|
||||||
|
|
||||||
const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]);
|
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 algorithm = certificate.getSignatureAlgorithmField();
|
||||||
const signatureHex = certificate.getSignatureValueHex();
|
const signatureHex = certificate.getSignatureValueHex();
|
||||||
|
|
@ -142,14 +143,14 @@ export class TwoFactorAuthenticationService {
|
||||||
challenge: string
|
challenge: string
|
||||||
}) {
|
}) {
|
||||||
if (clientData.type !== 'webauthn.get') {
|
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) {
|
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) {
|
if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
|
||||||
throw new Error('origin mismatch');
|
throw ErrorHandling('origin mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationData = Buffer.concat(
|
const verificationData = Buffer.concat(
|
||||||
|
|
@ -171,11 +172,11 @@ export class TwoFactorAuthenticationService {
|
||||||
const negTwo = publicKey.get(-2);
|
const negTwo = publicKey.get(-2);
|
||||||
|
|
||||||
if (!negTwo || negTwo.length !== 32) {
|
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);
|
const negThree = publicKey.get(-3);
|
||||||
if (!negThree || negThree.length !== 32) {
|
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(
|
const publicKeyU2F = Buffer.concat(
|
||||||
|
|
@ -206,7 +207,7 @@ export class TwoFactorAuthenticationService {
|
||||||
credentialId: Buffer,
|
credentialId: Buffer,
|
||||||
}) {
|
}) {
|
||||||
if (attStmt.alg !== -7) {
|
if (attStmt.alg !== -7) {
|
||||||
throw new Error('alg mismatch');
|
throw ErrorHandling('alg mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationData = Buffer.concat([
|
const verificationData = Buffer.concat([
|
||||||
|
|
@ -219,11 +220,11 @@ export class TwoFactorAuthenticationService {
|
||||||
const negTwo = publicKey.get(-2);
|
const negTwo = publicKey.get(-2);
|
||||||
|
|
||||||
if (!negTwo || negTwo.length !== 32) {
|
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);
|
const negThree = publicKey.get(-3);
|
||||||
if (!negThree || negThree.length !== 32) {
|
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(
|
const publicKeyData = Buffer.concat(
|
||||||
|
|
@ -232,7 +233,7 @@ export class TwoFactorAuthenticationService {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!attCert.equals(publicKeyData)) {
|
if (!attCert.equals(publicKeyData)) {
|
||||||
throw new Error('public key mismatch');
|
throw ErrorHandling('public key mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = crypto
|
const isValid = crypto
|
||||||
|
|
@ -278,7 +279,7 @@ export class TwoFactorAuthenticationService {
|
||||||
const signature = jwsParts[2];
|
const signature = jwsParts[2];
|
||||||
|
|
||||||
if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) {
|
if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) {
|
||||||
throw new Error('invalid nonce');
|
throw ErrorHandling('invalid nonce');
|
||||||
}
|
}
|
||||||
|
|
||||||
const certificateChain = header.x5c
|
const certificateChain = header.x5c
|
||||||
|
|
@ -286,11 +287,11 @@ export class TwoFactorAuthenticationService {
|
||||||
.concat([GSR2]);
|
.concat([GSR2]);
|
||||||
|
|
||||||
if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
|
if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
|
||||||
throw new Error('invalid common name');
|
throw ErrorHandling('invalid common name');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!verifyCertificateChain(certificateChain)) {
|
if (!verifyCertificateChain(certificateChain)) {
|
||||||
throw new Error('Invalid certificate chain!');
|
throw ErrorHandling('Invalid certificate chain!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const signatureBase = Buffer.from(
|
const signatureBase = Buffer.from(
|
||||||
|
|
@ -306,11 +307,11 @@ export class TwoFactorAuthenticationService {
|
||||||
const negTwo = publicKey.get(-2);
|
const negTwo = publicKey.get(-2);
|
||||||
|
|
||||||
if (!negTwo || negTwo.length !== 32) {
|
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);
|
const negThree = publicKey.get(-3);
|
||||||
if (!negThree || negThree.length !== 32) {
|
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(
|
const publicKeyData = Buffer.concat(
|
||||||
|
|
@ -355,11 +356,11 @@ export class TwoFactorAuthenticationService {
|
||||||
const negTwo = publicKey.get(-2);
|
const negTwo = publicKey.get(-2);
|
||||||
|
|
||||||
if (!negTwo || negTwo.length !== 32) {
|
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);
|
const negThree = publicKey.get(-3);
|
||||||
if (!negThree || negThree.length !== 32) {
|
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(
|
const publicKeyData = Buffer.concat(
|
||||||
|
|
@ -373,11 +374,11 @@ export class TwoFactorAuthenticationService {
|
||||||
};
|
};
|
||||||
} else if (attStmt.ecdaaKeyId) {
|
} else if (attStmt.ecdaaKeyId) {
|
||||||
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
|
// 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 {
|
} 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;
|
const x5c: Buffer[] = attStmt.x5c;
|
||||||
if (x5c.length !== 1) {
|
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];
|
const attCert = x5c[0];
|
||||||
|
|
@ -410,11 +411,11 @@ export class TwoFactorAuthenticationService {
|
||||||
const negTwo: Buffer = publicKey.get(-2);
|
const negTwo: Buffer = publicKey.get(-2);
|
||||||
|
|
||||||
if (!negTwo || negTwo.length !== 32) {
|
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);
|
const negThree: Buffer = publicKey.get(-3);
|
||||||
if (!negThree || negThree.length !== 32) {
|
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(
|
const publicKeyU2F = Buffer.concat(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
import Logger from '../logger.js';
|
import Logger from '../logger.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const logger = new Logger('following/create');
|
const logger = new Logger('following/create');
|
||||||
|
|
||||||
|
|
@ -459,8 +460,8 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
this.userBlockingService.checkBlocked(followee.id, follower.id),
|
this.userBlockingService.checkBlocked(followee.id, follower.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (blocking) throw new Error('blocking');
|
if (blocking) throw ErrorHandling('blocking');
|
||||||
if (blocked) throw new Error('blocked');
|
if (blocked) throw ErrorHandling('blocked');
|
||||||
|
|
||||||
const followRequest = await this.followRequestsRepository.insert({
|
const followRequest = await this.followRequestsRepository.insert({
|
||||||
id: this.idService.genId(),
|
id: this.idService.genId(),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
import { LdSignatureService } from './LdSignatureService.js';
|
import { LdSignatureService } from './LdSignatureService.js';
|
||||||
import { ApMfmService } from './ApMfmService.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 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()
|
@Injectable()
|
||||||
export class ApRendererService {
|
export class ApRendererService {
|
||||||
|
|
@ -98,7 +99,7 @@ export class ApRendererService {
|
||||||
to = [`${attributedTo}/followers`];
|
to = [`${attributedTo}/followers`];
|
||||||
cc = [];
|
cc = [];
|
||||||
} else {
|
} else {
|
||||||
throw new Error('renderAnnounce: cannot render non-public note');
|
throw ErrorHandling('renderAnnounce: cannot render non-public note');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -120,7 +121,7 @@ export class ApRendererService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderBlock(block: Blocking): IBlock {
|
public renderBlock(block: Blocking): IBlock {
|
||||||
if (block.blockee?.uri == null) {
|
if (block.blockee?.uri == null) {
|
||||||
throw new Error('renderBlock: missing blockee uri');
|
throw ErrorHandling('renderBlock: missing blockee uri');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
|
|
@ -60,7 +61,7 @@ export class Resolver {
|
||||||
if (isCollectionOrOrderedCollection(collection)) {
|
if (isCollectionOrOrderedCollection(collection)) {
|
||||||
return collection;
|
return collection;
|
||||||
} else {
|
} 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
|
// URLs with fragment parts cannot be resolved correctly because
|
||||||
// the fragment part does not get transmitted over HTTP(S).
|
// the fragment part does not get transmitted over HTTP(S).
|
||||||
// Avoid strange behaviour by not trying to resolve these at all.
|
// 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)) {
|
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) {
|
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);
|
this.history.add(value);
|
||||||
|
|
@ -94,7 +95,7 @@ export class Resolver {
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
||||||
throw new Error('Instance is blocked');
|
throw ErrorHandling('Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.signToActivityPubGet && !this.user) {
|
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'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
||||||
object['@context'] !== '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;
|
return object;
|
||||||
|
|
@ -119,7 +120,7 @@ export class Resolver {
|
||||||
@bindThis
|
@bindThis
|
||||||
private resolveLocal(url: string): Promise<IObject> {
|
private resolveLocal(url: string): Promise<IObject> {
|
||||||
const parsed = this.apDbResolverService.parseUri(url);
|
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) {
|
switch (parsed.type) {
|
||||||
case 'notes':
|
case 'notes':
|
||||||
|
|
@ -147,14 +148,14 @@ export class Resolver {
|
||||||
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
|
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
|
||||||
case 'follows':
|
case 'follows':
|
||||||
// rest should be <followee id>
|
// 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(
|
return Promise.all(
|
||||||
[parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
|
[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)));
|
.then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url)));
|
||||||
default:
|
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 { ApImageService } from './ApImageService.js';
|
||||||
import type { Resolver } from '../ApResolverService.js';
|
import type { Resolver } from '../ApResolverService.js';
|
||||||
import type { IObject, IPost } from '../type.js';
|
import type { IObject, IPost } from '../type.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApNoteService {
|
export class ApNoteService {
|
||||||
|
|
@ -80,16 +81,28 @@ export class ApNoteService {
|
||||||
const expectHost = this.utilityService.extractDbHost(uri);
|
const expectHost = this.utilityService.extractDbHost(uri);
|
||||||
|
|
||||||
if (!validPost.includes(getApType(object))) {
|
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) {
|
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));
|
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
|
||||||
if (object.attributedTo && actualHost !== expectHost) {
|
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;
|
return null;
|
||||||
|
|
@ -123,7 +136,7 @@ export class ApNoteService {
|
||||||
value,
|
value,
|
||||||
object,
|
object,
|
||||||
});
|
});
|
||||||
throw new Error('invalid note');
|
throw ErrorHandling('invalid note');
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = object as IPost;
|
const note = object as IPost;
|
||||||
|
|
@ -131,27 +144,27 @@ export class ApNoteService {
|
||||||
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
||||||
|
|
||||||
if (note.id && !checkHttps(note.id)) {
|
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);
|
const url = getOneApHrefNullable(note.url);
|
||||||
|
|
||||||
if (url && !checkHttps(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}`);
|
this.logger.info(`Creating the Note: ${note.id}`);
|
||||||
|
|
||||||
// 投稿者をフェッチ
|
// 投稿者をフェッチ
|
||||||
if (note.attributedTo == null) {
|
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;
|
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as RemoteUser;
|
||||||
|
|
||||||
// 投稿者が凍結されていたらスキップ
|
// 投稿者が凍結されていたらスキップ
|
||||||
if (actor.isSuspended) {
|
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);
|
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
|
||||||
|
|
@ -186,7 +199,7 @@ export class ApNoteService {
|
||||||
.then(x => {
|
.then(x => {
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
this.logger.warn('Specified inReplyTo, but not found');
|
this.logger.warn('Specified inReplyTo, but not found');
|
||||||
throw new Error('inReplyTo not found');
|
throw ErrorHandling('inReplyTo not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return x;
|
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);
|
quote = results.filter((x): x is { status: 'ok', res: Note } => x.status === 'ok').map(x => x.res).at(0);
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
if (results.some(x => x.status === 'temperror')) {
|
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');
|
this.logger.info('The note is already inserted while creating itself, reading again');
|
||||||
const duplicate = await this.fetchNote(value);
|
const duplicate = await this.fetchNote(value);
|
||||||
if (!duplicate) {
|
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;
|
return duplicate;
|
||||||
}
|
}
|
||||||
|
|
@ -376,7 +389,7 @@ export class ApNoteService {
|
||||||
});
|
});
|
||||||
|
|
||||||
const emoji = await this.emojisRepository.findOneBy({ host, name });
|
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;
|
return emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||||
import type { Note } from '@/models/entities/Note.js';
|
import type { Note } from '@/models/entities/Note.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetterService {
|
export class GetterService {
|
||||||
|
|
@ -61,7 +62,7 @@ export class GetterService {
|
||||||
const user = await this.getUser(userId);
|
const user = await this.getUser(userId);
|
||||||
|
|
||||||
if (!this.userEntityService.isRemoteUser(user)) {
|
if (!this.userEntityService.isRemoteUser(user)) {
|
||||||
throw new Error('user is not a remote user');
|
throw ErrorHandling('user is not a remote user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|
@ -75,7 +76,7 @@ export class GetterService {
|
||||||
const user = await this.getUser(userId);
|
const user = await this.getUser(userId);
|
||||||
|
|
||||||
if (!this.userEntityService.isLocalUser(user)) {
|
if (!this.userEntityService.isLocalUser(user)) {
|
||||||
throw new Error('user is not a local user');
|
throw ErrorHandling('user is not a local user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
@ -67,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
try {
|
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 {
|
} catch {
|
||||||
throw new ApiError(meta.errors.invalidUrl);
|
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 { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
@ -53,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (user == null || profile == null) {
|
if (user == null || profile == null) {
|
||||||
throw new Error('user not found');
|
throw ErrorHandling('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isModerator = await this.roleService.isModerator(user);
|
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 });
|
const _me = await this.usersRepository.findOneByOrFail({ id: me.id });
|
||||||
if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) {
|
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 });
|
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 { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['antennas'],
|
tags: ['antennas'],
|
||||||
|
|
@ -87,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
if ((ps.keywords.length === 0) || ps.keywords[0].every(x => x === '')) {
|
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({
|
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 type { UserProfilesRepository } from '@/models/index.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
@ -41,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||||
|
|
||||||
if (profile.twoFactorTempSecret == null) {
|
if (profile.twoFactorTempSecret == null) {
|
||||||
throw new Error('二段階認証の設定が開始されていません');
|
throw ErrorHandling('二段階認証の設定が開始されていません');
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta = OTPAuth.TOTP.validate({
|
const delta = OTPAuth.TOTP.validate({
|
||||||
|
|
@ -52,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (delta === null) {
|
if (delta === null) {
|
||||||
throw new Error('not verified');
|
throw ErrorHandling('not verified');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userProfilesRepository.update(me.id, {
|
await this.userProfilesRepository.update(me.id, {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
|
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
|
||||||
import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
|
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!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw ErrorHandling('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!profile.twoFactorEnabled) {
|
if (!profile.twoFactorEnabled) {
|
||||||
throw new Error('2fa not enabled');
|
throw ErrorHandling('2fa not enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientData = JSON.parse(ps.clientDataJSON);
|
const clientData = JSON.parse(ps.clientDataJSON);
|
||||||
|
|
||||||
if (clientData.type !== 'webauthn.create') {
|
if (clientData.type !== 'webauthn.create') {
|
||||||
throw new Error('not a creation attestation');
|
throw ErrorHandling('not a creation attestation');
|
||||||
}
|
}
|
||||||
if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
|
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'));
|
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);
|
const rpIdHash = attestation.authData.slice(0, 32);
|
||||||
if (!rpIdHashReal.equals(rpIdHash)) {
|
if (!rpIdHashReal.equals(rpIdHash)) {
|
||||||
throw new Error('rpIdHash mismatch');
|
throw ErrorHandling('rpIdHash mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
const flags = attestation.authData[32];
|
const flags = attestation.authData[32];
|
||||||
|
|
||||||
// eslint:disable-next-line:no-bitwise
|
// eslint:disable-next-line:no-bitwise
|
||||||
if (!(flags & 1)) {
|
if (!(flags & 1)) {
|
||||||
throw new Error('user not present');
|
throw ErrorHandling('user not present');
|
||||||
}
|
}
|
||||||
|
|
||||||
const authData = Buffer.from(attestation.authData);
|
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 publicKeyData = authData.slice(55 + credentialIdLength);
|
||||||
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
|
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
|
||||||
if (publicKey.get(3) !== -7) {
|
if (publicKey.get(3) !== -7) {
|
||||||
throw new Error('alg mismatch');
|
throw ErrorHandling('alg mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
const procedures = this.twoFactorAuthenticationService.getProcedures();
|
const procedures = this.twoFactorAuthenticationService.getProcedures();
|
||||||
|
|
||||||
if (!(procedures as any)[attestation.fmt]) {
|
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({
|
const verificationData = (procedures as any)[attestation.fmt].verify({
|
||||||
|
|
@ -119,7 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
publicKey,
|
publicKey,
|
||||||
rpIdHash,
|
rpIdHash,
|
||||||
});
|
});
|
||||||
if (!verificationData.valid) throw new Error('signature invalid');
|
if (!verificationData.valid) throw ErrorHandling('signature invalid');
|
||||||
|
|
||||||
const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
|
const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
@ -129,7 +130,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!attestationChallenge) {
|
if (!attestationChallenge) {
|
||||||
throw new Error('non-existent challenge');
|
throw ErrorHandling('non-existent challenge');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.attestationChallengesRepository.delete({
|
await this.attestationChallengesRepository.delete({
|
||||||
|
|
@ -142,7 +143,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
new Date().getTime() - attestationChallenge.createdAt.getTime() >=
|
new Date().getTime() - attestationChallenge.createdAt.getTime() >=
|
||||||
5 * 60 * 1000
|
5 * 60 * 1000
|
||||||
) {
|
) {
|
||||||
throw new Error('expired challenge');
|
throw ErrorHandling('expired challenge');
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentialIdString = credentialId.toString('hex');
|
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 { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
@ -43,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw ErrorHandling('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate user's secret key
|
// Generate user's secret key
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
@ -46,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw ErrorHandling('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we only delete the user's own creds
|
// 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 type { UserProfilesRepository } from '@/models/index.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
@ -42,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw ErrorHandling('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userProfilesRepository.update(me.id, {
|
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 { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
@ -47,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw ErrorHandling('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.deleteAccountService.deleteAccount(me);
|
await this.deleteAccountService.deleteAccount(me);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import * as Redis from 'ioredis';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { resetDb } from '@/misc/reset-db.js';
|
import { resetDb } from '@/misc/reset-db.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['non-productive'],
|
tags: ['non-productive'],
|
||||||
|
|
@ -39,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
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 redisClient.flushdb();
|
||||||
await resetDb(this.db);
|
await resetDb(this.db);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js';
|
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ErrorHandling } from '@/error.js'; // TODO Line 51
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['reset password'],
|
tags: ['reset password'],
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { AntennaChannelService } from './channels/antenna.js';
|
||||||
import { DriveChannelService } from './channels/drive.js';
|
import { DriveChannelService } from './channels/drive.js';
|
||||||
import { HashtagChannelService } from './channels/hashtag.js';
|
import { HashtagChannelService } from './channels/hashtag.js';
|
||||||
import { RoleTimelineChannelService } from './channels/role-timeline.js';
|
import { RoleTimelineChannelService } from './channels/role-timeline.js';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ChannelsService {
|
export class ChannelsService {
|
||||||
|
|
@ -59,7 +60,7 @@ export class ChannelsService {
|
||||||
case 'admin': return this.adminChannelService;
|
case 'admin': return this.adminChannelService;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`no such channel: ${name}`);
|
throw ErrorHandling(`no such channel: ${name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import Logger from '@/logger.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import type { ServerResponse } from 'node:http';
|
import type { ServerResponse } from 'node:http';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
// TODO: Consider migrating to @node-oauth/oauth2-server once
|
// TODO: Consider migrating to @node-oauth/oauth2-server once
|
||||||
// https://github.com/node-oauth/node-oauth2-server/issues/180 is figured out.
|
// 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) => {
|
fastify.get('/oauth/authorize', async (request, reply) => {
|
||||||
const oauth2 = (request.raw as MiddlewareRequest).oauth2;
|
const oauth2 = (request.raw as MiddlewareRequest).oauth2;
|
||||||
if (!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}"`);
|
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
|
@ -147,17 +148,17 @@ export class ClientServerService {
|
||||||
const token = request.cookies.token;
|
const token = request.cookies.token;
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
reply.code(401);
|
reply.code(401);
|
||||||
throw new Error('login required');
|
throw ErrorHandling('login required');
|
||||||
}
|
}
|
||||||
const user = await this.usersRepository.findOneBy({ token });
|
const user = await this.usersRepository.findOneBy({ token });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
reply.code(403);
|
reply.code(403);
|
||||||
throw new Error('no such user');
|
throw ErrorHandling('no such user');
|
||||||
}
|
}
|
||||||
const isAdministrator = await this.roleService.isAdministrator(user);
|
const isAdministrator = await this.roleService.isAdministrator(user);
|
||||||
if (!isAdministrator) {
|
if (!isAdministrator) {
|
||||||
reply.code(403);
|
reply.code(403);
|
||||||
throw new Error('access denied');
|
throw ErrorHandling('access denied');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApiError } from '@/server/api/error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { ErrorHandling } from '@/error.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UrlPreviewService {
|
export class UrlPreviewService {
|
||||||
|
|
@ -84,11 +85,11 @@ export class UrlPreviewService {
|
||||||
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
||||||
|
|
||||||
if (!(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
|
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://'))) {
|
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);
|
summary.icon = this.wrap(summary.icon);
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ import { deviceKind } from '@/scripts/device-kind';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { versatileLang } from '@/scripts/intl-const';
|
import { versatileLang } from '@/scripts/intl-const';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||||
|
|
||||||
|
|
@ -124,7 +125,7 @@ let tweetHeight = $ref(150);
|
||||||
let unknownUrl = $ref(false);
|
let unknownUrl = $ref(false);
|
||||||
|
|
||||||
const requestUrl = new URL(props.url);
|
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') {
|
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
|
||||||
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
|
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import { versatileLang } from '@/scripts/intl-const';
|
import { versatileLang } from '@/scripts/intl-const';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
url: string;
|
url: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const requestUrl = new URL(props.url);
|
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 fetching = $ref(true);
|
||||||
let title = $ref<string | null>(null);
|
let title = $ref<string | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { url as local } from '@/config';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { useTooltip } from '@/scripts/use-tooltip';
|
import { useTooltip } from '@/scripts/use-tooltip';
|
||||||
import { safeURIDecode } from '@/scripts/safe-uri-decode';
|
import { safeURIDecode } from '@/scripts/safe-uri-decode';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
url: string;
|
url: string;
|
||||||
|
|
@ -38,7 +39,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const self = props.url.startsWith(local);
|
const self = props.url.startsWith(local);
|
||||||
const url = new URL(props.url);
|
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();
|
const el = ref();
|
||||||
|
|
||||||
useTooltip(el, (showing) => {
|
useTooltip(el, (showing) => {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,7 @@ import * as os from '@/os';
|
||||||
import { $i, login } from '@/account';
|
import { $i, login } from '@/account';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -62,7 +63,7 @@ function accepted() {
|
||||||
state = 'accepted';
|
state = 'accepted';
|
||||||
if (session && session.app.callbackUrl) {
|
if (session && session.app.callbackUrl) {
|
||||||
const url = new URL(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}`;
|
location.href = `${session.app.callbackUrl}?token=${session.token}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import * as Acct from 'misskey-js/built/acct';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { mainRouter } from '@/router';
|
import { mainRouter } from '@/router';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
async function follow(user): Promise<void> {
|
async function follow(user): Promise<void> {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
|
@ -33,7 +34,7 @@ async function follow(user): Promise<void> {
|
||||||
|
|
||||||
const acct = new URL(location.href).searchParams.get('acct');
|
const acct = new URL(location.href).searchParams.get('acct');
|
||||||
if (acct == null) {
|
if (acct == null) {
|
||||||
throw new Error('acct required');
|
throw ErrorHandling('acct required');
|
||||||
}
|
}
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
|
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
|
||||||
import { dateString } from '@/filters/date';
|
import { dateString } from '@/filters/date';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
host: string;
|
host: string;
|
||||||
|
|
@ -173,8 +174,8 @@ async function fetch(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleBlock(): Promise<void> {
|
async function toggleBlock(): Promise<void> {
|
||||||
if (!meta) throw new Error('No meta?');
|
if (!meta) throw ErrorHandling('No meta?');
|
||||||
if (!instance) throw new Error('No instance?');
|
if (!instance) throw ErrorHandling('No instance?');
|
||||||
const { host } = instance;
|
const { host } = instance;
|
||||||
await os.api('admin/update-meta', {
|
await os.api('admin/update-meta', {
|
||||||
blockedHosts: isBlocked ? meta.blockedHosts.concat([host]) : meta.blockedHosts.filter(x => x !== host),
|
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> {
|
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', {
|
await os.api('admin/federation/update-instance', {
|
||||||
host: instance.host,
|
host: instance.host,
|
||||||
isSuspended: suspended,
|
isSuspended: suspended,
|
||||||
|
|
@ -190,7 +191,7 @@ async function toggleSuspend(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshMetadata(): 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', {
|
os.api('admin/federation/refresh-remote-instance-metadata', {
|
||||||
host: instance.host,
|
host: instance.host,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ import * as os from '@/os';
|
||||||
import { $i, login } from '@/account';
|
import { $i, login } from '@/account';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
session: string;
|
session: string;
|
||||||
|
|
@ -75,7 +76,7 @@ async function accept(): Promise<void> {
|
||||||
state = 'accepted';
|
state = 'accepted';
|
||||||
if (props.callback) {
|
if (props.callback) {
|
||||||
const cbUrl = new URL(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);
|
cbUrl.searchParams.set('session', props.session);
|
||||||
location.href = cbUrl.href;
|
location.href = cbUrl.href;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ import { unisonReload } from '@/scripts/unison-reload';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import { miLocalStorage } from '@/local-storage';
|
import { miLocalStorage } from '@/local-storage';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const lang = ref(miLocalStorage.getItem('lang'));
|
const lang = ref(miLocalStorage.getItem('lang'));
|
||||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||||
|
|
@ -276,7 +277,7 @@ function downloadEmojiIndex(lang: string) {
|
||||||
function download() {
|
function download() {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
|
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();
|
currentIndexes[lang] = await download();
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ import { i18n } from '@/i18n';
|
||||||
import { version, host } from '@/config';
|
import { version, host } from '@/config';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import { miLocalStorage } from '@/local-storage';
|
import { miLocalStorage } from '@/local-storage';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const { t, ts } = i18n;
|
const { t, ts } = i18n;
|
||||||
|
|
||||||
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
||||||
|
|
@ -133,27 +135,27 @@ function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(profile: any): void {
|
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
|
// 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.name) throw ErrorHandling('Missing required prop: name');
|
||||||
if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion');
|
if (!profile.misskeyVersion) throw ErrorHandling('Missing required prop: misskeyVersion');
|
||||||
|
|
||||||
// Check if createdAt and updatedAt is Date
|
// Check if createdAt and updatedAt is Date
|
||||||
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-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 (profile.updatedAt) {
|
||||||
if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) {
|
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) {
|
} 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 (!profile.settings) throw ErrorHandling('Missing required prop: settings');
|
||||||
if (!isObject(profile.settings)) throw new Error('Invalid prop: settings');
|
if (!isObject(profile.settings)) throw ErrorHandling('Invalid prop: settings');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSettings(): Profile['settings'] {
|
function getSettings(): Profile['settings'] {
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@ export function pleaseLogin(path?: string) {
|
||||||
},
|
},
|
||||||
}, 'closed');
|
}, 'closed');
|
||||||
|
|
||||||
throw new Error('signin required');
|
console.log('signin required');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
const dateTimeIntervals = {
|
const dateTimeIntervals = {
|
||||||
'day': 86400000,
|
'day': 86400000,
|
||||||
'hour': 3600000,
|
'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])
|
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!d) throw new Error('wrong number of arguments');
|
if (!d) throw ErrorHandling('wrong number of arguments');
|
||||||
|
|
||||||
return new Date(d);
|
return new Date(d);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { apiUrl } from '@/config';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
import { alert } from '@/os';
|
import { alert } from '@/os';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { ErrorHandling } from '@/error';
|
||||||
|
|
||||||
type Uploading = {
|
type Uploading = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -34,7 +35,7 @@ export function uploadFile(
|
||||||
name?: string,
|
name?: string,
|
||||||
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
|
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
|
||||||
): Promise<Misskey.entities.DriveFile> {
|
): 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;
|
if (folder && typeof folder === 'object') folder = folder.id;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue