From 8b7532dc3d28fb7d9f9f0cdcb22b8f408fe7f19f Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 20 Sep 2021 14:48:02 +0800 Subject: [PATCH] feat: pageproxy debug mode --- package.json | 1 + pnpm-lock.yaml | 41 ++++++++++++ src/app.config.ts | 9 ++- src/common/adapt/fastify.ts | 19 +++++- src/modules/pageproxy/pageproxy.controller.ts | 66 ++++++++++++++++--- src/modules/pageproxy/pageproxy.dto.ts | 24 +++++++ 6 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 src/modules/pageproxy/pageproxy.dto.ts diff --git a/package.json b/package.json index 69f17491..2d8d4448 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "dotenv": "*", "ejs": "3.1.6", "fastify-multipart": "5.0.0", + "fastify-secure-session": "^2.3.1", "fastify-swagger": "4.12.0", "graphql": "15.5.3", "html-minifier": "4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f3cfedd..e5c2d0eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,7 @@ specifiers: eslint: '*' fastify: '*' fastify-multipart: 5.0.0 + fastify-secure-session: ^2.3.1 fastify-swagger: 4.12.0 graphql: 15.5.3 html-minifier: 4.0.0 @@ -137,6 +138,7 @@ dependencies: dotenv: 10.0.0 ejs: 3.1.6 fastify-multipart: 5.0.0 + fastify-secure-session: 2.3.1 fastify-swagger: 4.12.0 graphql: 15.5.3 html-minifier: 4.0.0 @@ -3390,6 +3392,11 @@ packages: safe-buffer: 5.1.2 dev: true + /cookie-signature/1.1.0: + resolution: {integrity: sha512-Alvs19Vgq07eunykd3Xy2jF0/qSNv2u7KDbAek9H5liV1UMijbqFs5cycZvv5dVsvseT/U4H8/7/w8Koh35C4A==} + engines: {node: '>=6.6.0'} + dev: false + /cookie/0.4.1: resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} engines: {node: '>= 0.6'} @@ -4169,6 +4176,14 @@ packages: fastify-plugin: 2.3.4 dev: false + /fastify-cookie/5.3.1: + resolution: {integrity: sha512-ubiC5ydvgqaXOydeg3G+SFEiTJtsyNPde6ForXi+UG66aDbsHlWNLJTQZg4BopXv+MJ0SzlShGVCWv26mgAwpw==} + dependencies: + cookie: 0.4.1 + cookie-signature: 1.1.0 + fastify-plugin: 3.0.0 + dev: false + /fastify-cors/6.0.2: resolution: {integrity: sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==} dependencies: @@ -4207,6 +4222,15 @@ packages: resolution: {integrity: sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==} dev: false + /fastify-secure-session/2.3.1: + resolution: {integrity: sha512-6XsatyRSiX0UQB0MOPlU/PC9yn3seImefS1yv8C0bAyjAJ876839eHKPrclwNKBVX+9SUX+LdJGJTBi8DSU63g==} + hasBin: true + dependencies: + fastify-cookie: 5.3.1 + fastify-plugin: 3.0.0 + sodium-native: 3.2.1 + dev: false + /fastify-static/4.2.3: resolution: {integrity: sha512-uFRgwYXZwLKyaMrByf10efO+HTjAPqyQOlUthoGljQKGCfbwUeTeE7EHadsDWeN7NMeqBE617RamVh9uqatuUw==} dependencies: @@ -4836,6 +4860,10 @@ packages: /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + /inquirer/7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} @@ -6447,6 +6475,11 @@ packages: engines: {node: 4.x || >=6.0.0} dev: false + /node-gyp-build/4.3.0: + resolution: {integrity: sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==} + hasBin: true + dev: false + /node-int64/0.4.0: resolution: {integrity: sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=} dev: true @@ -7581,6 +7614,14 @@ packages: - supports-color - utf-8-validate + /sodium-native/3.2.1: + resolution: {integrity: sha512-EgDZ/Z7PxL2kCasKk7wnRkV8W9kvwuIlHuHXAxkQm3FF0MgVsjyLBXGjSRGhjE6u7rhSpk3KaMfFM23bfMysIQ==} + requiresBuild: true + dependencies: + ini: 1.3.8 + node-gyp-build: 4.3.0 + dev: false + /sonic-boom/1.4.1: resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==} dependencies: diff --git a/src/app.config.ts b/src/app.config.ts index cda1dbc0..fbb34ad2 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,6 +1,6 @@ import type { AxiosRequestConfig } from 'axios' -import { argv } from 'yargs' - +import yargs from 'yargs' +const argv = yargs.argv as any console.log(argv) export const API_VERSION = 2 @@ -47,4 +47,9 @@ export const SECURITY = { jwtExpire: '7d', // 跳过登陆鉴权 skipAuth: argv.skipAuth ?? false, + get secret() { + return this.jwtSecret + }, + // 必须 16 位 + salt: argv.salt || 'axczswrasxzfqxsa', } diff --git a/src/common/adapt/fastify.ts b/src/common/adapt/fastify.ts index 227b88f6..62d0ac70 100644 --- a/src/common/adapt/fastify.ts +++ b/src/common/adapt/fastify.ts @@ -1,10 +1,14 @@ import { FastifyAdapter } from '@nestjs/platform-fastify' import FastifyMultipart from 'fastify-multipart' -export const fastifyApp: FastifyAdapter = new FastifyAdapter({ +import secureSession from 'fastify-secure-session' +import { SECURITY } from '~/app.config' + +const app: FastifyAdapter = new FastifyAdapter({ trustProxy: true, }) +export { app as fastifyApp } -fastifyApp.register(FastifyMultipart, { +app.register(FastifyMultipart, { limits: { fields: 10, // Max number of non-file fields fileSize: 1024 * 1024 * 6, // limit size 6M @@ -12,7 +16,7 @@ fastifyApp.register(FastifyMultipart, { }, }) -fastifyApp.getInstance().addHook('onRequest', (request, reply, done) => { +app.getInstance().addHook('onRequest', (request, reply, done) => { const origin = request.headers.origin if (!origin) { request.headers.origin = request.headers.host @@ -20,3 +24,12 @@ fastifyApp.getInstance().addHook('onRequest', (request, reply, done) => { done() }) + +app.register(secureSession, { + secret: SECURITY.secret.slice(10).repeat(4), + salt: SECURITY.salt, + cookie: { + path: '/', + httpOnly: true, + }, +}) diff --git a/src/modules/pageproxy/pageproxy.controller.ts b/src/modules/pageproxy/pageproxy.controller.ts index 40c26359..4d40e61b 100644 --- a/src/modules/pageproxy/pageproxy.controller.ts +++ b/src/modules/pageproxy/pageproxy.controller.ts @@ -1,4 +1,5 @@ -import { Controller, Get, Header } from '@nestjs/common' +import { Controller, Get, Header, Query, Session } from '@nestjs/common' +import * as secureSession from 'fastify-secure-session' import { API_VERSION } from '~/app.config' import { HTTPDecorators } from '~/common/decorator/http.decorator' import { ApiName } from '~/common/decorator/openapi.decorator' @@ -7,6 +8,7 @@ import { CacheService } from '~/processors/cache/cache.service' import { getRedisKey } from '~/utils/redis.util' import { ConfigsService } from '../configs/configs.service' import { InitService } from '../init/init.service' +import { PageProxyDebugDto } from './pageproxy.dto' interface IInjectableData { BASE_API: null | string @@ -30,7 +32,10 @@ export class PageProxyController { @Get('/qaqdmin') @Header('Content-Type', 'text/html') @HTTPDecorators.Bypass - async proxyAdmin() { + async proxyAdmin( + @Session() session: secureSession.Session, + @Query() query: PageProxyDebugDto, + ) { const { adminExtra, url: { webUrl }, @@ -38,24 +43,57 @@ export class PageProxyController { if (!adminExtra.enableAdminProxy && !isDev) { return '

Admin Proxy is disabled

' } + const { + __apiUrl: apiUrl, + __gatewayUrl: gatewayUrl, + __onlyGithub: onlyGithub, + __debug: debug, + } = query + session.options({ maxAge: 1000 * 60 * 10 }) + if (apiUrl) { + session.set('__apiUrl', apiUrl) + } + + if (gatewayUrl) { + session.set('__gatewayUrl', gatewayUrl) + } + + if (debug === false) { + session.delete() + } let entry = - (await this.cacheService.get(getRedisKey(RedisKeys.AdminPage))) || + (!onlyGithub && + (await this.cacheService.get( + getRedisKey(RedisKeys.AdminPage), + ))) || (await (async () => { const indexEntryUrl = `https://raw.githubusercontent.com/mx-space/admin-next/gh-pages/index.html` const indexEntryCdnUrl = `https://cdn.jsdelivr.net/gh/mx-space/admin-next@gh-pages/index.html?t=${+new Date()}` - return await Promise.any([ + const tasks = [ // 龟兔赛跑, 乌龟先跑 // eslint-disable-next-line @typescript-eslint/no-empty-function fetch(indexEntryUrl).then((res) => res.text()), - sleep(1000).then(async () => (await fetch(indexEntryCdnUrl)).text()), - ]) + ] + if (!onlyGithub) { + tasks.push( + sleep(1000).then(async () => + (await fetch(indexEntryCdnUrl)).text(), + ), + ) + } + return await Promise.any(tasks) })()) await this.cacheService.set(getRedisKey(RedisKeys.AdminPage), entry, { ttl: 10 * 60, }) + const sessionInjectableData = { + BASE_API: session.get('__apiUrl'), + GATEWAY: session.get('__gatewayUrl'), + } + entry = entry.replace( ``, ``, ) return entry diff --git a/src/modules/pageproxy/pageproxy.dto.ts b/src/modules/pageproxy/pageproxy.dto.ts new file mode 100644 index 00000000..1e6fa9b2 --- /dev/null +++ b/src/modules/pageproxy/pageproxy.dto.ts @@ -0,0 +1,24 @@ +import { Transform } from 'class-transformer' +import { IsBoolean, IsIn, IsOptional, IsUrl } from 'class-validator' + +export class PageProxyDebugDto { + @IsIn([false]) + @IsOptional() + @Transform(({ value }) => (value === 'false' ? false : true)) + __debug: boolean + @IsUrl({ require_protocol: true }) + @IsOptional() + __apiUrl?: string + + @IsUrl({ require_protocol: true }) + @IsOptional() + __gatewayUrl?: string + + @IsBoolean() + @IsOptional() + @Transform(({ value }) => (value === 'true' ? true : false)) + /** + * If true, always use index.html pull from github. + */ + __onlyGithub = false +}