重构 JWKS 模块,新增 JWKS 管理器,优化密钥生成与管理逻辑,更新路由处理
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -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
|
||||
/pages
|
||||
|
||||
storage
|
||||
@@ -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';
|
||||
|
||||
@@ -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<string> {
|
||||
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()
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
18
src/routes-simple/routes/jwks.ts
Normal file
18
src/routes-simple/routes/jwks.ts
Normal file
@@ -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 }));
|
||||
// });
|
||||
28
src/test/jwks.ts
Normal file
28
src/test/jwks.ts
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user