feat: [frontend / backend] update git urls, update userid watermark, authenticate image proxy and url previews
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
export class AddSomeUrls1557761316509 {
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://git.leafus.net/pawkey/pawkey'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://git.leafus.net/pawkey/pawkey/issues/new/choose'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://git.pawlickers.org/pawkey/pawkey'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://git.pawlickers.org/pawkey/pawkey/issues/new'`);
|
||||
}
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "feedbackUrl"`);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: pawinput and Pawkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ChangeGITUrls1763436210524 {
|
||||
name = 'ChangeGITUrls1763436210524'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://git.pawlickers.org/pawkey/pawkey'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://git.pawlickers.org/pawkey/pawkey/issues/new'`);
|
||||
await queryRunner.query(`UPDATE "meta" SET "repositoryUrl"=DEFAULT WHERE "repositoryUrl" IN ('https://git.leafus.net/pawkey/pawkey')`);
|
||||
await queryRunner.query(`UPDATE "meta" SET "feedbackUrl"=DEFAULT WHERE "feedbackUrl" IN ('https://git.leafus.net/pawkey/pawkey/-/issues/new')`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://git.leafus.net/pawkey/pawkey'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://git.leafus.net/pawkey/pawkey/-/issues/new'`);
|
||||
await queryRunner.query(`UPDATE "meta" SET "repositoryUrl"=DEFAULT WHERE "repositoryUrl" IN ('https://git.pawlickers.org/pawkey/pawkey')`);
|
||||
await queryRunner.query(`UPDATE "meta" SET "feedbackUrl"=DEFAULT WHERE "feedbackUrl" IN ('https://git.pawlickers.org/pawkey/pawkey/issues/new')`);
|
||||
}
|
||||
}
|
||||
@@ -483,7 +483,7 @@ export function loadConfig(): Config {
|
||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
||||
: null,
|
||||
userAgent: `Misskey/${version} (${config.url})`,
|
||||
userAgent: `Pawkey/${version} (${config.url})`,
|
||||
frontendEntry: frontendManifest['src/_boot_.ts'],
|
||||
frontendManifestExists: frontendManifestExists,
|
||||
frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
|
||||
|
||||
@@ -458,14 +458,14 @@ export class MiMeta {
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
default: 'https://git.leafus.net/pawkey/pawkey/',
|
||||
default: 'https://git.pawlickers.org/pawkey/pawkey/',
|
||||
nullable: false,
|
||||
})
|
||||
public repositoryUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
default: 'https://git.leafus.net/pawkey/pawkey/-/issues/new',
|
||||
default: 'https://git.pawlickers.org/pawkey/pawkey/issues/new',
|
||||
nullable: true,
|
||||
})
|
||||
public feedbackUrl: string | null;
|
||||
|
||||
@@ -108,6 +108,43 @@ export class FileServerService {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = request.method === 'GET'
|
||||
? request.query
|
||||
: request.body;
|
||||
|
||||
let cookieToken: string | undefined;
|
||||
if (request.headers.cookie) {
|
||||
const cookies = request.headers.cookie.split(';').reduce((acc, cookie) => {
|
||||
const [key, value] = cookie.trim().split('=');
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
cookieToken = cookies.token;
|
||||
}
|
||||
|
||||
const token = request.headers.authorization?.startsWith('Bearer ')
|
||||
? request.headers.authorization.slice(7)
|
||||
: (body as any)?.['i'] ?? cookieToken;
|
||||
|
||||
if (token != null && typeof token !== 'string') {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const [user] = await this.authenticateService.authenticate(token);
|
||||
|
||||
if (!user) {
|
||||
reply.code(401);
|
||||
reply.send({
|
||||
error: {
|
||||
message: 'Credential required.',
|
||||
code: 'CREDENTIAL_REQUIRED',
|
||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const keyUrl = new URL(url);
|
||||
keyUrl.searchParams.forEach(k => keyUrl.searchParams.delete(k));
|
||||
keyUrl.hash = '';
|
||||
@@ -632,7 +669,7 @@ export class FileServerService {
|
||||
// https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive)
|
||||
const token = request.headers.authorization?.startsWith('Bearer ')
|
||||
? request.headers.authorization.slice(7)
|
||||
: body?.['i'];
|
||||
: (body as any)?.['i'];
|
||||
if (token != null && typeof token !== 'string') {
|
||||
reply.code(400);
|
||||
return false;
|
||||
|
||||
@@ -95,12 +95,27 @@ export class StreamingApiServerService {
|
||||
? request.headers.authorization.slice(7)
|
||||
: q.get('i');
|
||||
|
||||
// Require authentication for WebSocket connections
|
||||
if (!token) {
|
||||
socket.write([
|
||||
'HTTP/1.1 401 Unauthorized',
|
||||
'WWW-Authenticate: Bearer realm="Misskey"',
|
||||
].join('\r\n') + '\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
[user, app] = await this.authenticateService.authenticate(token);
|
||||
|
||||
if (app !== null && !app.permission.some(p => p === 'read:account')) {
|
||||
throw new AuthenticationError('Your app does not have necessary permissions to use websocket API.');
|
||||
}
|
||||
|
||||
// Ensure we have a valid user
|
||||
if (!user) {
|
||||
throw new AuthenticationError('Invalid token or user not found.');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof AuthenticationError) {
|
||||
socket.write([
|
||||
|
||||
@@ -19,7 +19,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
||||
|
||||
externalDocs: {
|
||||
description: 'Repository',
|
||||
url: 'https://git.leafus.net/pawkey/pawkey',
|
||||
url: 'https://git.pawlickers.org/pawkey/pawkey',
|
||||
},
|
||||
|
||||
servers: [{
|
||||
@@ -97,7 +97,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
||||
description: desc,
|
||||
externalDocs: {
|
||||
description: 'Source code',
|
||||
url: `https://git.leafus.net/pawkey/pawkey/-/tree/stable/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
|
||||
url: `https://git.pawlickers.org/pawkey/pawkey/src/branch/stable/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
|
||||
},
|
||||
...(endpoint.meta.tags ? {
|
||||
tags: [endpoint.meta.tags[0]],
|
||||
|
||||
@@ -169,8 +169,22 @@ export class UrlPreviewService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
// Check authentication
|
||||
const auth = await this.authenticate(request);
|
||||
const [user] = auth;
|
||||
|
||||
if (!user) {
|
||||
reply.code(401);
|
||||
return reply.send({
|
||||
error: {
|
||||
message: 'Credential required.',
|
||||
code: 'CREDENTIAL_REQUIRED',
|
||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if (!await this.checkRateLimit(auth, reply)) {
|
||||
return;
|
||||
}
|
||||
@@ -450,10 +464,21 @@ export class UrlPreviewService {
|
||||
private async authenticate(request: FastifyRequest<{ Querystring?: { i?: string | string[] }, Body?: { i?: string | string[] } }>): Promise<AuthArray> {
|
||||
const body = request.method === 'GET' ? request.query : request.body;
|
||||
|
||||
// Parse token from cookie header if present
|
||||
let cookieToken: string | undefined;
|
||||
if (request.headers.cookie) {
|
||||
const cookies = request.headers.cookie.split(';').reduce((acc, cookie) => {
|
||||
const [key, value] = cookie.trim().split('=');
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
cookieToken = cookies.token;
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive)
|
||||
const token = request.headers.authorization?.startsWith('Bearer ')
|
||||
? request.headers.authorization.slice(7)
|
||||
: body?.['i'];
|
||||
: body?.['i'] ?? cookieToken;
|
||||
if (token != null && typeof token !== 'string') {
|
||||
return [undefined, undefined, getIpHash(request.ip)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user