Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop

This commit is contained in:
syuilo 2022-06-03 23:08:18 +09:00
commit 71c230b7b7
18 changed files with 85 additions and 167 deletions

4
.github/labeler.yml vendored
View file

@ -4,5 +4,9 @@
'🖥Client': '🖥Client':
- packages/client/**/* - packages/client/**/*
'🧪Test':
- cypress/**/*
- packages/backend/test/**/*
'‼️ wrong locales': '‼️ wrong locales':
- any: ['locales/*.yml', '!locales/ja-JP.yml'] - any: ['locales/*.yml', '!locales/ja-JP.yml']

View file

@ -1,6 +1,8 @@
name: "Pull Request Labeler" name: "Pull Request Labeler"
on: on:
- pull_request_target pull_request_target:
branches-ignore:
- 'l10n_develop'
jobs: jobs:
triage: triage:

View file

@ -26,7 +26,7 @@ You should also include the user name that made the change.
Your own theme color may be unset if it was in an invalid format. Your own theme color may be unset if it was in an invalid format.
Admins should check their instance settings if in doubt. Admins should check their instance settings if in doubt.
- Perform port diagnosis at startup only when Listen fails @mei23 - Perform port diagnosis at startup only when Listen fails @mei23
- Rate limiting is now also usable for non-authenticated users. @Johann150 - Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address. Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
### Bugfixes ### Bugfixes
@ -43,6 +43,7 @@ You should also include the user name that made the change.
- Server: use correct order of attachments on notes @Johann150 - Server: use correct order of attachments on notes @Johann150
- Server: prevent crash when processing certain PNGs @syuilo - Server: prevent crash when processing certain PNGs @syuilo
- Server: Fix unable to generate video thumbnails @mei23 - Server: Fix unable to generate video thumbnails @mei23
- Server: Fix `Cannot find module` issue @mei23
## 12.110.1 (2022/04/23) ## 12.110.1 (2022/04/23)

View file

@ -1,11 +1,6 @@
describe('Before setup instance', () => { describe('Before setup instance', () => {
beforeEach(() => { beforeEach(() => {
cy.window(win => { cy.resetState();
win.indexedDB.deleteDatabase('keyval-store');
});
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
}); });
afterEach(() => { afterEach(() => {
@ -35,18 +30,10 @@ describe('Before setup instance', () => {
describe('After setup instance', () => { describe('After setup instance', () => {
beforeEach(() => { beforeEach(() => {
cy.window(win => { cy.resetState();
win.indexedDB.deleteDatabase('keyval-store');
});
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.request('POST', '/api/admin/accounts/create', { cy.registerUser('admin', 'pass', true);
username: 'admin',
password: 'pass',
}).its('body').as('admin');
}); });
afterEach(() => { afterEach(() => {
@ -76,24 +63,13 @@ describe('After setup instance', () => {
describe('After user signup', () => { describe('After user signup', () => {
beforeEach(() => { beforeEach(() => {
cy.window(win => { cy.resetState();
win.indexedDB.deleteDatabase('keyval-store');
});
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.request('POST', '/api/admin/accounts/create', { cy.registerUser('admin', 'pass', true);
username: 'admin',
password: 'pass',
}).its('body').as('admin');
// ユーザー作成 // ユーザー作成
cy.request('POST', '/api/signup', { cy.registerUser('alice', 'alice1234');
username: 'alice',
password: 'alice1234',
}).its('body').as('alice');
}); });
afterEach(() => { afterEach(() => {
@ -138,34 +114,15 @@ describe('After user signup', () => {
describe('After user singed in', () => { describe('After user singed in', () => {
beforeEach(() => { beforeEach(() => {
cy.window(win => { cy.resetState();
win.indexedDB.deleteDatabase('keyval-store');
});
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.request('POST', '/api/admin/accounts/create', { cy.registerUser('admin', 'pass', true);
username: 'admin',
password: 'pass',
}).its('body').as('admin');
// ユーザー作成 // ユーザー作成
cy.request('POST', '/api/signup', { cy.registerUser('alice', 'alice1234');
username: 'alice',
password: 'alice1234',
}).its('body').as('alice');
cy.visit('/'); cy.login('alice', 'alice1234');
cy.intercept('POST', '/api/signin').as('signin');
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice');
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
cy.wait('@signin').as('signedIn');
}); });
afterEach(() => { afterEach(() => {

View file

@ -1,34 +1,15 @@
describe('After user signed in', () => { describe('After user signed in', () => {
beforeEach(() => { beforeEach(() => {
cy.window(win => { cy.resetState();
win.indexedDB.deleteDatabase('keyval-store');
});
cy.viewport('macbook-16'); cy.viewport('macbook-16');
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.request('POST', '/api/admin/accounts/create', { cy.registerUser('admin', 'pass', true);
username: 'admin',
password: 'pass',
}).its('body').as('admin');
// ユーザー作成 // ユーザー作成
cy.request('POST', '/api/signup', { cy.registerUser('alice', 'alice1234');
username: 'alice',
password: 'alice1234',
}).its('body').as('alice');
cy.visit('/'); cy.login('alice', 'alice1234');
cy.intercept('POST', '/api/signin').as('signin');
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice');
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
cy.wait('@signin').as('signedIn');
}); });
afterEach(() => { afterEach(() => {

View file

@ -23,3 +23,33 @@
// //
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
Cypress.Commands.add('resetState', () => {
cy.window(win => {
win.indexedDB.deleteDatabase('keyval-store');
});
cy.request('POST', '/api/reset-db').as('reset');
cy.get('@reset').its('status').should('equal', 204);
cy.reload(true);
});
Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
cy.request('POST', route, {
username: username,
password: password,
}).its('body').as(username);
});
Cypress.Commands.add('login', (username, password) => {
cy.visit('/');
cy.intercept('POST', '/api/signin').as('signin');
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type(username);
cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
cy.wait('@signin').as('signedIn');
});

View file

@ -6,6 +6,9 @@ const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
export function fromHtml(html: string, hashtagNames?: string[]): string { export function fromHtml(html: string, hashtagNames?: string[]): string {
// some AP servers like Pixelfed use br tags as well as newlines
html = html.replace(/<br\s?\/?>\r?\n/gi, '\n');
const dom = parse5.parseFragment(html); const dom = parse5.parseFragment(html);
let text = ''; let text = '';

View file

@ -0,0 +1,9 @@
import IPCIDR from 'ip-cidr';
export function getIpHash(ip: string) {
// because a single person may control many IPv6 addresses,
// only a /64 subnet prefix of any IP will be taken into account.
// (this means for IPv4 the entire address is used)
const prefix = IPCIDR.createAddress(ip).mask(64);
return 'ip-' + BigInt('0b' + prefix).toString(36);
}

View file

@ -305,11 +305,13 @@ export default function() {
systemQueue.add('resyncCharts', { systemQueue.add('resyncCharts', {
}, { }, {
repeat: { cron: '0 0 * * *' }, repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
}); });
systemQueue.add('cleanCharts', { systemQueue.add('cleanCharts', {
}, { }, {
repeat: { cron: '0 0 * * *' }, repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
}); });
systemQueue.add('checkExpiredMutings', { systemQueue.add('checkExpiredMutings', {

View file

@ -6,7 +6,7 @@ import endpoints, { IEndpointMeta } from './endpoints.js';
import { ApiError } from './error.js'; import { ApiError } from './error.js';
import { apiLogger } from './logger.js'; import { apiLogger } from './logger.js';
import { AccessToken } from '@/models/entities/access-token.js'; import { AccessToken } from '@/models/entities/access-token.js';
import IPCIDR from 'ip-cidr'; import { getIpHash } from '@/misc/get-ip-hash.js';
const accessDenied = { const accessDenied = {
message: 'Access denied.', message: 'Access denied.',
@ -33,18 +33,13 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
throw new ApiError(accessDenied); throw new ApiError(accessDenied);
} }
if (ep.meta.requireCredential && ep.meta.limit && !isModerator) { if (ep.meta.limit && !isModerator) {
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
let limitActor: string; let limitActor: string;
if (user) { if (user) {
limitActor = user.id; limitActor = user.id;
} else { } else {
// because a single person may control many IPv6 addresses, limitActor = getIpHash(ctx!.ip);
// only a /64 subnet prefix of any IP will be taken into account.
// (this means for IPv4 the entire address is used)
const ip = IPCIDR.createAddress(ctx.ip).mask(64);
limitActor = 'ip-' + parseInt(ip, 2).toString(36);
} }
const limit = Object.assign({}, ep.meta.limit); const limit = Object.assign({}, ep.meta.limit);

View file

@ -10,6 +10,7 @@ import { verifyLogin, hash } from '../2fa.js';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { limiter } from '../limiter.js'; import { limiter } from '../limiter.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
export default async (ctx: Koa.Context) => { export default async (ctx: Koa.Context) => {
ctx.set('Access-Control-Allow-Origin', config.url); ctx.set('Access-Control-Allow-Origin', config.url);
@ -27,7 +28,7 @@ export default async (ctx: Koa.Context) => {
try { try {
// not more than 1 attempt per second and not more than 10 attempts per hour // not more than 1 attempt per second and not more than 10 attempts per hour
await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, ctx.ip); await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
} catch (err) { } catch (err) {
ctx.status = 429; ctx.status = 429;
ctx.body = { ctx.body = {

View file

@ -312,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
endedPollNotificationQueue.add({ endedPollNotificationQueue.add({
noteId: note.id, noteId: note.id,
}, { }, {
delay delay,
removeOnComplete: true,
}); });
} }

View file

@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue';
import MkButton from '@/components/ui/button.vue'; import MkButton from '@/components/ui/button.vue';
import * as os from '@/os'; import * as os from '@/os';
import { login } from '@/account'; import { login } from '@/account';
import { appendQuery, query } from '@/scripts/url';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -82,7 +83,9 @@ export default defineComponent({
this.state = 'accepted'; this.state = 'accepted';
if (this.callback) { if (this.callback) {
location.href = `${this.callback}?session=${this.session}`; location.href = appendQuery(this.callback, query({
session: this.session
}));
} }
}, },
deny() { deny() {

View file

@ -35,7 +35,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineExpose, ref } from 'vue'; import { computed, defineExpose, ref } from 'vue';
import * as tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import FormSwitch from '@/components/form/switch.vue'; import FormSwitch from '@/components/form/switch.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';

View file

@ -94,10 +94,10 @@ function onStats(connStats) {
inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`; inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`; outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0]; inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1]; inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0]; outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1]; outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
inRecent = connStats.net.rx; inRecent = connStats.net.rx;
outRecent = connStats.net.tx; outRecent = connStats.net.tx;

View file

@ -1,71 +0,0 @@
/**
* webpack configuration
*/
const fs = require('fs');
const webpack = require('webpack');
class WebpackOnBuildPlugin {
constructor(callback) {
this.callback = callback;
}
apply(compiler) {
compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback);
}
}
const isProduction = process.env.NODE_ENV === 'production';
const locales = require('../../locales');
const meta = require('../../package.json');
module.exports = {
target: 'webworker',
entry: {
['sw-lib']: './src/lib.ts'
},
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: [{
loader: 'ts-loader',
options: {
happyPackMode: true,
transpileOnly: true,
configFile: __dirname + '/tsconfig.json',
}
}]
}]
},
plugins: [
new webpack.ProgressPlugin({}),
new webpack.DefinePlugin({
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
_ENV_: JSON.stringify(process.env.NODE_ENV),
_DEV_: process.env.NODE_ENV !== 'production',
_PERF_PREFIX_: JSON.stringify('Misskey:'),
}),
],
output: {
path: __dirname + '/../../built/_sw_dist_',
filename: `[name].js`,
publicPath: `/`,
pathinfo: false,
},
resolve: {
extensions: [
'.js', '.ts', '.json'
],
alias: {
'@': __dirname + '/src/',
}
},
resolveLoader: {
modules: ['node_modules']
},
devtool: false, //'source-map',
mode: isProduction ? 'production' : 'development'
};

View file

@ -3,7 +3,7 @@ const execa = require('execa');
(async () => { (async () => {
console.log('installing dependencies of packages/backend ...'); console.log('installing dependencies of packages/backend ...');
await execa('yarn', ['install'], { await execa('yarn', ['--force', 'install'], {
cwd: __dirname + '/../packages/backend', cwd: __dirname + '/../packages/backend',
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,