重构 JWKS 模块,新增 JWKS 管理器,优化密钥生成与管理逻辑,更新路由处理
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,11 +2,8 @@ node_modules
|
|||||||
|
|
||||||
dist
|
dist
|
||||||
|
|
||||||
app.config.json5
|
|
||||||
|
|
||||||
apps.config.json
|
apps.config.json
|
||||||
|
|
||||||
deploy.tar.gz
|
|
||||||
cache-file
|
cache-file
|
||||||
|
|
||||||
/apps
|
/apps
|
||||||
@@ -22,6 +19,7 @@ release/*
|
|||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
pack-dist
|
pack-dist
|
||||||
app.config.json5.envision
|
|
||||||
|
|
||||||
/pages
|
/pages
|
||||||
|
|
||||||
|
storage
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { app } from './app.ts';
|
import { app } from './app.ts';
|
||||||
import './route.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 { port } from './modules/config.ts';
|
||||||
import { wssFun } from './modules/ws-proxy/index.ts';
|
import { wssFun } from './modules/ws-proxy/index.ts';
|
||||||
import { WebSocketListenerFun, HttpListenerFun } from '@kevisual/router/src/server/server-type.js';
|
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 { generate } from '@kevisual/auth'
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -14,7 +15,6 @@ export const getPath = async (dir: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const jwksGenerate = async (opts: { dir: string }) => {
|
export const jwksGenerate = async (opts: { dir: string }) => {
|
||||||
const dir = path.isAbsolute(opts.dir) ? opts.dir : path.join(process.cwd(), opts.dir);
|
const dir = path.isAbsolute(opts.dir) ? opts.dir : path.join(process.cwd(), opts.dir);
|
||||||
if (!fs.existsSync(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_KEY_PATH, privatePEM);
|
||||||
fs.writeFileSync(PRIVATE_JWK_PATH, JSON.stringify(privateJWK, null, 2));
|
fs.writeFileSync(PRIVATE_JWK_PATH, JSON.stringify(privateJWK, null, 2));
|
||||||
fs.writeFileSync(JWKS_PATH, JSON.stringify(jwks, 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