init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
|
dist
|
||||||
|
|
||||||
|
jwt
|
||||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||||
|
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY}
|
||||||
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
20
bun.config.ts
Normal file
20
bun.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { resolvePath } from '@kevisual/use-config';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const entry = 'src/index.ts';
|
||||||
|
const naming = 'app';
|
||||||
|
const external = ['pm2'];
|
||||||
|
await Bun.build({
|
||||||
|
target: 'browser',
|
||||||
|
format: 'esm',
|
||||||
|
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||||
|
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||||
|
naming: {
|
||||||
|
entry: `${naming}.js`,
|
||||||
|
},
|
||||||
|
external,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmd ='dts -i src/index.ts -o app.d.ts'
|
||||||
|
execSync(cmd, { stdio: 'inherit' });
|
||||||
|
console.log('Build completed.');
|
||||||
35
package.json
Normal file
35
package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "@kevisual/auth",
|
||||||
|
"version": "2.0.1",
|
||||||
|
"description": "",
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun run bun.config.ts"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"keywords": [],
|
||||||
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
|
"license": "MIT",
|
||||||
|
"packageManager": "pnpm@10.28.1",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@kevisual/types": "^0.0.12",
|
||||||
|
"@kevisual/use-config": "^1.0.28",
|
||||||
|
"@kevisual/query": "^0.0.38",
|
||||||
|
"@kevisual/router": "^0.0.60",
|
||||||
|
"@types/bun": "^1.3.6",
|
||||||
|
"@types/node": "^25.0.10",
|
||||||
|
"es-toolkit": "^1.44.0",
|
||||||
|
"jose": "^6.1.3"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/app.js",
|
||||||
|
"./src/*": "./dist/src/*"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
132
pnpm-lock.yaml
generated
Normal file
132
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@kevisual/query':
|
||||||
|
specifier: ^0.0.38
|
||||||
|
version: 0.0.38
|
||||||
|
'@kevisual/router':
|
||||||
|
specifier: ^0.0.60
|
||||||
|
version: 0.0.60
|
||||||
|
'@kevisual/types':
|
||||||
|
specifier: ^0.0.12
|
||||||
|
version: 0.0.12
|
||||||
|
'@kevisual/use-config':
|
||||||
|
specifier: ^1.0.28
|
||||||
|
version: 1.0.28(dotenv@17.2.3)
|
||||||
|
'@types/bun':
|
||||||
|
specifier: ^1.3.6
|
||||||
|
version: 1.3.6
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^25.0.10
|
||||||
|
version: 25.0.10
|
||||||
|
es-toolkit:
|
||||||
|
specifier: ^1.44.0
|
||||||
|
version: 1.44.0
|
||||||
|
jose:
|
||||||
|
specifier: ^6.1.3
|
||||||
|
version: 6.1.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@kevisual/load@0.0.6':
|
||||||
|
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
|
||||||
|
|
||||||
|
'@kevisual/query@0.0.38':
|
||||||
|
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
|
||||||
|
|
||||||
|
'@kevisual/router@0.0.60':
|
||||||
|
resolution: {integrity: sha512-2v/ZzUstsaq+Uqo+tZX9ys5E+/2erPggCtljv9jTb3NA88ZdHsYUAsd5wUFvLtf9QucpJCzyWEt+InDV/98FKw==}
|
||||||
|
|
||||||
|
'@kevisual/types@0.0.12':
|
||||||
|
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||||
|
|
||||||
|
'@kevisual/use-config@1.0.28':
|
||||||
|
resolution: {integrity: sha512-ngF+LDbjxpXWrZNmnShIKF/jPpAa+ezV+DcgoZIIzHlRnIjE+rr9sLkN/B7WJbiH9C/j1tQXOILY8ujBqILrow==}
|
||||||
|
peerDependencies:
|
||||||
|
dotenv: ^17
|
||||||
|
|
||||||
|
'@types/bun@1.3.6':
|
||||||
|
resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==}
|
||||||
|
|
||||||
|
'@types/node@25.0.10':
|
||||||
|
resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==}
|
||||||
|
|
||||||
|
bun-types@1.3.6:
|
||||||
|
resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==}
|
||||||
|
|
||||||
|
dotenv@17.2.3:
|
||||||
|
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
es-toolkit@1.44.0:
|
||||||
|
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
|
||||||
|
|
||||||
|
eventemitter3@5.0.4:
|
||||||
|
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
||||||
|
|
||||||
|
hono@4.11.5:
|
||||||
|
resolution: {integrity: sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==}
|
||||||
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
|
jose@6.1.3:
|
||||||
|
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||||
|
|
||||||
|
tslib@2.8.1:
|
||||||
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
undici-types@7.16.0:
|
||||||
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@kevisual/load@0.0.6':
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 5.0.4
|
||||||
|
|
||||||
|
'@kevisual/query@0.0.38':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@kevisual/router@0.0.60':
|
||||||
|
dependencies:
|
||||||
|
hono: 4.11.5
|
||||||
|
|
||||||
|
'@kevisual/types@0.0.12': {}
|
||||||
|
|
||||||
|
'@kevisual/use-config@1.0.28(dotenv@17.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@kevisual/load': 0.0.6
|
||||||
|
dotenv: 17.2.3
|
||||||
|
|
||||||
|
'@types/bun@1.3.6':
|
||||||
|
dependencies:
|
||||||
|
bun-types: 1.3.6
|
||||||
|
|
||||||
|
'@types/node@25.0.10':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.16.0
|
||||||
|
|
||||||
|
bun-types@1.3.6:
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 25.0.10
|
||||||
|
|
||||||
|
dotenv@17.2.3: {}
|
||||||
|
|
||||||
|
es-toolkit@1.44.0: {}
|
||||||
|
|
||||||
|
eventemitter3@5.0.4: {}
|
||||||
|
|
||||||
|
hono@4.11.5: {}
|
||||||
|
|
||||||
|
jose@6.1.3: {}
|
||||||
|
|
||||||
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
undici-types@7.16.0: {}
|
||||||
53
readme.md
Normal file
53
readme.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
## JWT Configuration
|
||||||
|
|
||||||
|
### Convex auth.config.ts
|
||||||
|
|
||||||
|
issuer: https://convex.kevisual.cn
|
||||||
|
applicationID: convex-app
|
||||||
|
|
||||||
|
issuer必须与JWT中的iss字段匹配,applicationID必须与aud字段匹配。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { AuthConfig } from 'convex/server';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
type: 'customJwt',
|
||||||
|
applicationID: 'convex-app',
|
||||||
|
issuer: 'https://convex.kevisual.cn',
|
||||||
|
jwks: 'https://api-convex.kevisual.cn/root/convex/jwks.json',
|
||||||
|
algorithm: 'RS256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payload 例子
|
||||||
|
|
||||||
|
header必须包含kid字段以匹配jwks中的密钥ID。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as jose from "jose";
|
||||||
|
// 加载测试私钥
|
||||||
|
const keys = JSON.parse(await Bun.file("./jwt/privateKey.json").text());
|
||||||
|
const privateKey = await jose.importJWK(keys, "RS256");
|
||||||
|
|
||||||
|
// 生成 RS256 JWT
|
||||||
|
const payload = {
|
||||||
|
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 token = await new jose.SignJWT(payload)
|
||||||
|
.setProtectedHeader({
|
||||||
|
"alg": "RS256",
|
||||||
|
"typ": "JWT",
|
||||||
|
"kid": "kid-key-1"
|
||||||
|
})
|
||||||
|
.setIssuedAt()
|
||||||
|
.sign(privateKey);
|
||||||
|
```
|
||||||
123
src/auth.ts
Normal file
123
src/auth.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import * as jose from 'jose';
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 验证 JWT
|
||||||
|
* @param token JWT 字符串
|
||||||
|
* @param publicKey 公钥,可以是 CryptoKey、PEM 字符串或 JWK/JWKS 对象
|
||||||
|
* @returns 解码后的有效载荷
|
||||||
|
* @throws 如果验证失败或令牌无效,则抛出错误
|
||||||
|
*/
|
||||||
|
export async function verifyJWT(token: string, publicKey: jose.CryptoKey | string | jose.JWK) {
|
||||||
|
try {
|
||||||
|
let key: any;
|
||||||
|
if (typeof publicKey === 'string') {
|
||||||
|
key = await jose.importSPKI(publicKey, 'RS256')
|
||||||
|
} else if (typeof publicKey === 'object' && publicKey !== null && 'keys' in publicKey) {
|
||||||
|
// JWKS 格式
|
||||||
|
key = jose.createLocalJWKSet(publicKey as jose.JSONWebKeySet);
|
||||||
|
} else {
|
||||||
|
key = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payload } = await jose.jwtVerify(token, key, {
|
||||||
|
algorithms: ['RS256'],
|
||||||
|
});
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jose.errors.JWTExpired) {
|
||||||
|
console.error('JWT has expired:');
|
||||||
|
throw new Error('Token has expired');
|
||||||
|
}
|
||||||
|
console.error('JWT verification failed:', error);
|
||||||
|
throw new Error('Invalid token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export type JWTPayload<T = {}> = {
|
||||||
|
/**
|
||||||
|
* 会设置默认值: "https://convex.kevisual.cn"
|
||||||
|
*/
|
||||||
|
iss?: string,
|
||||||
|
/**
|
||||||
|
* "user:8fa2be73c2229e85"
|
||||||
|
* "ip:192.168.1.1"
|
||||||
|
*/
|
||||||
|
sub: string,
|
||||||
|
/**
|
||||||
|
* 会设置默认值:"convex-app"
|
||||||
|
*/
|
||||||
|
aud?: string,
|
||||||
|
/**
|
||||||
|
* 会设置默认值:当前时间 + 2 小时
|
||||||
|
*/
|
||||||
|
exp?: number,
|
||||||
|
/**
|
||||||
|
* 会设置默认值:当前时间
|
||||||
|
*/
|
||||||
|
iat?: number,
|
||||||
|
/**
|
||||||
|
* 其他自定义字段
|
||||||
|
*/
|
||||||
|
name?: string,
|
||||||
|
/**
|
||||||
|
* email
|
||||||
|
*/
|
||||||
|
email?: string
|
||||||
|
} & T;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 签发 JWT
|
||||||
|
* @param payload 有效载荷
|
||||||
|
* @param privateKey 私钥,可以是 CryptoKey、PEM 字符串或 JWK 对象
|
||||||
|
* @returns 签发的 JWT 字符串
|
||||||
|
*/
|
||||||
|
export async function signJWT(payload: JWTPayload, privateKey: jose.CryptoKey | string | jose.JWK): Promise<string> {
|
||||||
|
const expirationTime = Math.floor(Date.now() / 1000) + (2 * 60 * 60); // 2 hour from now
|
||||||
|
if (!payload.exp) {
|
||||||
|
payload.exp = expirationTime;
|
||||||
|
}
|
||||||
|
let cryptoKey: jose.CryptoKey;
|
||||||
|
if (typeof privateKey === 'string') {
|
||||||
|
cryptoKey = await jose.importPKCS8(privateKey, "RS256") as jose.CryptoKey;
|
||||||
|
} else if (typeof privateKey === 'object' && privateKey !== null && 'kty' in privateKey) {
|
||||||
|
cryptoKey = await jose.importJWK(privateKey, "RS256") as jose.CryptoKey;
|
||||||
|
} else {
|
||||||
|
cryptoKey = privateKey as jose.CryptoKey;
|
||||||
|
}
|
||||||
|
const iss = payload.iss || "https://convex.kevisual.cn";
|
||||||
|
if (!payload.iss) {
|
||||||
|
payload.iss = iss;
|
||||||
|
}
|
||||||
|
if (!payload.iat) {
|
||||||
|
payload.iat = Math.floor(Date.now() / 1000);
|
||||||
|
}
|
||||||
|
if (!payload.aud) {
|
||||||
|
payload.aud = "convex-app";
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await new jose.SignJWT(payload)
|
||||||
|
.setProtectedHeader({
|
||||||
|
"alg": "RS256",
|
||||||
|
"typ": "JWT",
|
||||||
|
"kid": "kid-key-1"
|
||||||
|
})
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime(payload.exp)
|
||||||
|
.setIssuer(iss)
|
||||||
|
.setSubject(payload.sub || "")
|
||||||
|
.sign(cryptoKey);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单独解码 JWT,不验证签名
|
||||||
|
* @param token
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const decodeJWT = (token: string): JWTPayload => {
|
||||||
|
try {
|
||||||
|
const decoded = jose.decodeJwt(token);
|
||||||
|
return decoded as JWTPayload;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Invalid token');
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/generate.ts
Normal file
39
src/generate.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as jose from 'jose';
|
||||||
|
|
||||||
|
async function generateKeyPair() {
|
||||||
|
const { privateKey, publicKey } = await jose.generateKeyPair('RS256', {
|
||||||
|
modulusLength: 2048,
|
||||||
|
extractable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { privateKey, publicKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createJWKS(publicKey: CryptoKey, kid?: string) {
|
||||||
|
const jwk = await jose.exportJWK(publicKey);
|
||||||
|
// 添加 kid 字段
|
||||||
|
jwk.kid = kid || 'kid-key-1';
|
||||||
|
const jwks = {
|
||||||
|
keys: [jwk]
|
||||||
|
};
|
||||||
|
return jwks;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateOpts = {
|
||||||
|
kid?: string;
|
||||||
|
}
|
||||||
|
export const generate = async (opts: GenerateOpts = {}) => {
|
||||||
|
const { privateKey, publicKey } = await generateKeyPair();
|
||||||
|
const jwks = await createJWKS(publicKey, opts.kid);
|
||||||
|
|
||||||
|
// 将私钥和 JWKS 保存到文件
|
||||||
|
const privateJWK = await jose.exportJWK(privateKey);
|
||||||
|
const privatePEM = await jose.exportPKCS8(privateKey);
|
||||||
|
const publicPEM = await jose.exportSPKI(publicKey);
|
||||||
|
return {
|
||||||
|
jwks,
|
||||||
|
privateJWK,
|
||||||
|
privatePEM,
|
||||||
|
publicPEM
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './auth.ts';
|
||||||
8
src/jwks/common.ts
Normal file
8
src/jwks/common.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
const dir = path.join(process.cwd(), 'jwt');
|
||||||
|
|
||||||
|
export const JWKS_PATH = path.join(dir, 'jwks.json');
|
||||||
|
export const PRIVATE_JWK_PATH = path.join(dir, 'privateKey.json');
|
||||||
|
|
||||||
|
export const PRIVATE_KEY_PATH = path.join(dir, 'privateKey.txt');
|
||||||
|
export const PUBLIC_KEY_PATH = path.join(dir, 'publicKey.txt');
|
||||||
23
src/jwks/create.ts
Normal file
23
src/jwks/create.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import * as common from './common.ts';
|
||||||
|
import { generate } from '../generate.ts'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { jwks, privateJWK, privatePEM, publicPEM } = await generate();
|
||||||
|
const dir = path.join(process.cwd(), 'jwt');
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(common.PUBLIC_KEY_PATH, publicPEM);
|
||||||
|
|
||||||
|
fs.writeFileSync(common.PRIVATE_KEY_PATH, privatePEM);
|
||||||
|
|
||||||
|
fs.writeFileSync(common.PRIVATE_JWK_PATH, JSON.stringify(privateJWK, null, 2));
|
||||||
|
fs.writeFileSync(common.JWKS_PATH, JSON.stringify(jwks, null, 2));
|
||||||
|
|
||||||
|
console.log('Private key and JWKS have been saved to files.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
22
src/jwks/get.ts
Normal file
22
src/jwks/get.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import * as common from './common.ts';
|
||||||
|
|
||||||
|
export const getValues = () => {
|
||||||
|
if (!fs.existsSync(common.JWKS_PATH)) {
|
||||||
|
throw new Error('JWKS file does not exist. Please create it first.');
|
||||||
|
}
|
||||||
|
const jwksData = fs.readFileSync(common.JWKS_PATH, 'utf-8');
|
||||||
|
const privateJWKData = fs.readFileSync(common.PRIVATE_JWK_PATH, 'utf-8');
|
||||||
|
const publicKeyPEM = fs.readFileSync(common.PUBLIC_KEY_PATH, 'utf-8');
|
||||||
|
const privateKeyPEM = fs.readFileSync(common.PRIVATE_KEY_PATH, 'utf-8');
|
||||||
|
|
||||||
|
return {
|
||||||
|
jwks: JSON.parse(jwksData),
|
||||||
|
privateJWK: JSON.parse(privateJWKData),
|
||||||
|
publicKeyPEM,
|
||||||
|
privateKeyPEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
2
src/router.ts
Normal file
2
src/router.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import type { App } from '@kevisual/router';
|
||||||
|
|
||||||
38
test/create.ts
Normal file
38
test/create.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import * as jose from 'jose';
|
||||||
|
import { signJWT, decodeJWT, verifyJWT } from '../src/auth.ts';
|
||||||
|
import { getValues } from '../src/jwks/get.ts';
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
iss: "https://convex.kevisual.cn",
|
||||||
|
sub: "user:123456",
|
||||||
|
aud: "convex-app",
|
||||||
|
name: "John Doe",
|
||||||
|
email: "john.doe@example.com",
|
||||||
|
// exp: Math.floor(Date.now() / 1000) - (2 * 60 * 60) // 2 hours from now
|
||||||
|
}
|
||||||
|
|
||||||
|
const createToken = async () => {
|
||||||
|
const { privateJWK, privateKeyPEM } = getValues();
|
||||||
|
const token = await signJWT(payload, privateKeyPEM);
|
||||||
|
console.log('Generated JWT:', token);
|
||||||
|
|
||||||
|
// console.log('expited at:', new Date(payload.exp * 1000).toLocaleString());
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
const token = await createToken()
|
||||||
|
|
||||||
|
const decode = decodeJWT(token)
|
||||||
|
|
||||||
|
console.log('Decoded JWT:', decode);
|
||||||
|
|
||||||
|
const verify = async () => {
|
||||||
|
const { publicKeyPEM, privateJWK, jwks } = getValues();
|
||||||
|
try {
|
||||||
|
const verifiedPayload = await verifyJWT(token, publicKeyPEM);
|
||||||
|
console.log('Verified JWT payload:', verifiedPayload);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Verification failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await verify();
|
||||||
Reference in New Issue
Block a user