1
0

feat: [frontend / backend] update git urls, update userid watermark, authenticate image proxy and url previews

This commit is contained in:
Leafus
2025-11-18 05:08:44 +01:00
parent 3fddd69ad8
commit ab15656458
25 changed files with 193 additions and 114 deletions
@@ -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')`);
}
}
+1 -1
View File
@@ -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'],
+2 -2
View File
@@ -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)];
}