diff --git a/.gitignore b/.gitignore index e5ebd65..b02e18e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,8 @@ node_modules dist -app.config.json5 - apps.config.json -deploy.tar.gz cache-file /apps @@ -22,6 +19,7 @@ release/* !.env.example pack-dist -app.config.json5.envision -/pages \ No newline at end of file +/pages + +storage \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index b362045..84fa437 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { app } from './app.ts'; import './route.ts'; -import { handleRequest } from './routes-simple/handle-request.ts'; +import { handleRequest } from './routes-simple/index.ts'; import { port } from './modules/config.ts'; import { wssFun } from './modules/ws-proxy/index.ts'; import { WebSocketListenerFun, HttpListenerFun } from '@kevisual/router/src/server/server-type.js'; diff --git a/src/modules/jwks/index.ts b/src/modules/jwks/index.ts index d24da36..1bf85fe 100644 --- a/src/modules/jwks/index.ts +++ b/src/modules/jwks/index.ts @@ -1,3 +1,4 @@ +import { signJWT, decodeJWT, type JWTPayload, verifyJWT } from '@kevisual/auth' import { generate } from '@kevisual/auth' import fs from 'node:fs'; import path from 'node:path'; @@ -14,7 +15,6 @@ export const getPath = async (dir: string) => { } } - export const jwksGenerate = async (opts: { dir: string }) => { const dir = path.isAbsolute(opts.dir) ? opts.dir : path.join(process.cwd(), opts.dir); if (!fs.existsSync(dir)) { @@ -26,6 +26,105 @@ export const jwksGenerate = async (opts: { dir: string }) => { fs.writeFileSync(PRIVATE_KEY_PATH, privatePEM); fs.writeFileSync(PRIVATE_JWK_PATH, JSON.stringify(privateJWK, null, 2)); fs.writeFileSync(JWKS_PATH, JSON.stringify(jwks, null, 2)); - console.log(`Keys have been saved to directory: ${dir}`); + console.log(`Keys 已保存到目录: ${dir}`); } + +interface JWKSPaths { + JWKS_PATH: string + PRIVATE_JWK_PATH: string + PRIVATE_KEY_PATH: string + PUBLIC_KEY_PATH: string +} + +interface JWKSContent { + jwks: string + privateJWK: string + privateKey: string + publicKey: string +} + +export class JWKSManager { + private paths: JWKSPaths | null = null + private content: JWKSContent | null = null + + constructor(private basePath?: string) { + this.basePath = basePath || path.join(process.cwd(), 'storage/jwks') + } + + async init() { + // 确保目录存在 + if (!fs.existsSync(this.basePath!)) { + fs.mkdirSync(this.basePath!, { recursive: true }) + } + + // 获取所有路径 + this.paths = await getPath(this.basePath!) + + // 如果 JWKS 文件不存在,则生成 + if (!fs.existsSync(this.paths.JWKS_PATH)) { + await jwksGenerate({ dir: this.basePath! }) + console.log(`JWKS 创建成功,路径: ${this.paths.JWKS_PATH}`) + } + + // 加载所有内容到内存 + await this.loadContent() + + return this + } + async checkInit() { + if (!this.content) { + await this.init() + } + } + private async loadContent() { + if (!this.paths) { + await this.init() + } + + this.content = { + jwks: fs.readFileSync(this.paths.JWKS_PATH, 'utf-8'), + privateJWK: fs.readFileSync(this.paths.PRIVATE_JWK_PATH, 'utf-8'), + privateKey: fs.readFileSync(this.paths.PRIVATE_KEY_PATH, 'utf-8'), + publicKey: fs.readFileSync(this.paths.PUBLIC_KEY_PATH, 'utf-8') + } + } + + async sign(payload: JWTPayload): Promise { + await this.checkInit() + return signJWT(payload, this.content.privateKey) + } + async verify(token: string) { + await this.checkInit() + return verifyJWT(token, this.content.publicKey) + } + async decode(token: string) { + await this.checkInit() + return decodeJWT(token) + } + async getJWKS() { + await this.checkInit() + return JSON.parse(this.content.jwks) + } + + async getPrivateJWK() { + await this.checkInit() + return JSON.parse(this.content.privateJWK) + } + + async getPublicKey() { + await this.checkInit() + return this.content.publicKey + } + + async getPrivateKey() { + await this.checkInit() + return this.content.privateKey + } + + getPaths() { + return this.paths + } +} + +export const manager = new JWKSManager() diff --git a/src/routes-simple/handle-request.ts b/src/routes-simple/handle-request.ts deleted file mode 100644 index fbb38fb..0000000 --- a/src/routes-simple/handle-request.ts +++ /dev/null @@ -1,31 +0,0 @@ -import http from 'node:http'; -import { router } from './router.ts'; -import './index.ts'; -import { handleRequest as PageProxy } from './page-proxy.ts'; - -const simpleAppsPrefixs = [ - "/api/wxmsg" -]; - - -export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => { - if (req.url?.startsWith('/api/router')) { - // router自己管理 - return; - } - // if (req.url === '/MP_verify_NGWvli5lGpEkByyt.txt') { - // res.writeHead(200, { 'Content-Type': 'text/plain' }); - // res.end('NGWvli5lGpEkByyt'); - // return; - // } - if (req.url && simpleAppsPrefixs.some(prefix => req.url!.startsWith(prefix))) { - // 简单应用路由处理 - // 设置跨域 - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - return router.parse(req, res); - } - // 其他请求交给页面代理处理 - return PageProxy(req, res); -}; diff --git a/src/routes-simple/index.ts b/src/routes-simple/index.ts index e69de29..bc590ac 100644 --- a/src/routes-simple/index.ts +++ b/src/routes-simple/index.ts @@ -0,0 +1,33 @@ +import http from 'node:http'; +import { router } from './router.ts'; +import { handleRequest as PageProxy } from './page-proxy.ts'; + +import './routes/jwks.ts' + +const simpleAppsPrefixs = [ + "/api/wxmsg", + "/api/convex/" +]; + + +export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => { + if (req.url?.startsWith('/api/router')) { + // router自己管理 + return; + } + // if (req.url === '/MP_verify_NGWvli5lGpEkByyt.txt') { + // res.writeHead(200, { 'Content-Type': 'text/plain' }); + // res.end('NGWvli5lGpEkByyt'); + // return; + // } + if (req.url && simpleAppsPrefixs.some(prefix => req.url!.startsWith(prefix))) { + // 简单应用路由处理 + // 设置跨域 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + return router.parse(req, res); + } + // 其他请求交给页面代理处理 + return PageProxy(req, res); +}; diff --git a/src/routes-simple/routes/jwks.ts b/src/routes-simple/routes/jwks.ts new file mode 100644 index 0000000..75a71fa --- /dev/null +++ b/src/routes-simple/routes/jwks.ts @@ -0,0 +1,18 @@ +import { router } from '@/app.ts' +import { manager } from '@/modules/jwks/index.ts' +router.all('/api/convex/jwks.json', async (req, res) => { + const jwks = await manager.getJWKS() + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(jwks)); +}) + +// rsync -avz kevisual:/root/kevisual/assistant-app/storage/jwks/ ./storage/jwks + +// router.all('/api/convex/sign', async (req, res) => { +// const payload = { +// sub: 'abc' +// }; +// const token = await manager.sign(payload); +// res.setHeader('Content-Type', 'application/json'); +// res.end(JSON.stringify({ token })); +// }); \ No newline at end of file diff --git a/src/test/jwks.ts b/src/test/jwks.ts new file mode 100644 index 0000000..f9c49fe --- /dev/null +++ b/src/test/jwks.ts @@ -0,0 +1,28 @@ +import { manager } from '@/modules/jwks/index.ts' + +await manager.init() + +// const value = await manager.sign({ +// sub: 'user:abc', +// email: 'x@xiongxiao.me', +// }) + +// console.log('value', value) + +const v2 = { + iss: "https://convex.kevisual.cn", + sub: "user:8fa2be73c2229e85", + aud: "convex-app", + exp: Math.floor(Date.now() / 1000) + 3600, + name: "Test User AA", + email: "test@example.com", +} +const value2 = await manager.sign(v2) +console.log('v2', value2) + +const token = '33' +// const decode = await manager.decode(token) +// console.log('decode', decode) + +// const verify = await manager.verify(token); +// console.log('verify', verify) \ No newline at end of file