@kevisual/auth 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bun.config.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { resolvePath } from '@kevisual/use-config';
2
+ import { execSync } from 'node:child_process';
3
+
4
+ const entry = 'src/index.ts';
5
+ const naming = 'app';
6
+ const external = ['pm2'];
7
+ await Bun.build({
8
+ target: 'browser',
9
+ format: 'esm',
10
+ entrypoints: [resolvePath(entry, { meta: import.meta })],
11
+ outdir: resolvePath('./dist', { meta: import.meta }),
12
+ naming: {
13
+ entry: `${naming}.js`,
14
+ },
15
+ external,
16
+ });
17
+
18
+ const cmd ='dts -i src/index.ts -o app.d.ts'
19
+ execSync(cmd, { stdio: 'inherit' });
20
+ console.log('Build completed.');
package/package.json CHANGED
@@ -1,62 +1,29 @@
1
1
  {
2
2
  "name": "@kevisual/auth",
3
- "version": "1.0.5",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
- "main": "dist/index.js",
6
- "type": "module",
7
- "typings": "dist/index.d.js",
8
- "private": false,
9
5
  "scripts": {
10
- "build": "npm run clean &&rollup -c",
11
- "clean": "rimraf dist"
6
+ "build": "bun run bun.config.ts"
12
7
  },
13
- "files": [
14
- "src",
15
- "dist"
16
- ],
17
- "keywords": [
18
- "router",
19
- "auth"
20
- ],
21
- "author": "",
22
- "license": "ISC",
8
+ "keywords": [],
9
+ "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
10
+ "license": "MIT",
11
+ "packageManager": "pnpm@10.26.0",
12
+ "type": "module",
13
+ "dependencies": {},
23
14
  "devDependencies": {
24
- "@rollup/plugin-commonjs": "^28.0.1",
25
- "@rollup/plugin-node-resolve": "^15.3.0",
26
- "@rollup/plugin-typescript": "^12.1.1",
27
- "@types/crypto-js": "^4.2.2",
28
- "@types/jsonwebtoken": "^9.0.7",
29
- "crypto-js": "^4.2.0",
30
- "jsonwebtoken": "^9.0.2",
31
- "rollup": "^4.28.1",
32
- "rollup-plugin-dts": "^6.1.1",
33
- "tslib": "^2.8.1"
34
- },
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/abearxiong/kevisual-router.git"
15
+ "@kevisual/types": "^0.0.12",
16
+ "@kevisual/use-config": "^1.0.28",
17
+ "@kevisual/query": "^0.0.38",
18
+ "@kevisual/router": "^0.0.60",
19
+ "@types/bun": "^1.3.6",
20
+ "@types/node": "^25.0.10",
21
+ "es-toolkit": "^1.44.0",
22
+ "jose": "^6.1.3"
38
23
  },
39
24
  "exports": {
40
- ".": {
41
- "import": "./dist/index.mjs",
42
- "require": "./dist/index.mjs"
43
- },
44
- "./token": {
45
- "import": "./dist/create-token.mjs",
46
- "require": "./dist/create-token.mjs"
47
- },
48
- "./salt": {
49
- "import": "./dist/create-token.mjs",
50
- "require": "./dist/create-token.mjs"
51
- },
52
- "./proxy": {
53
- "import": "./dist/proxy.mjs",
54
- "require": "./dist/proxy.mjs"
55
- },
56
- "./is-me": {
57
- "import": "./dist/is-me.mjs",
58
- "require": "./dist/is-me.mjs"
59
- }
25
+ ".": "./dist/app.js",
26
+ "./src/*": "./dist/src/*"
60
27
  },
61
28
  "publishConfig": {
62
29
  "access": "public"
package/readme.md CHANGED
@@ -1,26 +1,53 @@
1
- # router query auth
1
+ ## JWT Configuration
2
2
 
3
- for kevisual/router
3
+ ### Convex auth.config.ts
4
4
 
5
- 配置项
5
+ issuer: https://convex.kevisual.cn
6
+ applicationID: convex-app
6
7
 
7
- ```json5
8
- {
9
- tokenSercet: 'xx'
10
- }
8
+ issuer必须与JWT中的iss字段匹配,applicationID必须与aud字段匹配。
9
+
10
+ ```ts
11
+ import { AuthConfig } from 'convex/server';
12
+
13
+ export default {
14
+ providers: [
15
+ {
16
+ type: 'customJwt',
17
+ applicationID: 'convex-app',
18
+ issuer: 'https://convex.kevisual.cn',
19
+ jwks: 'https://api-convex.kevisual.cn/root/convex/jwks.json',
20
+ algorithm: 'RS256',
21
+ },
22
+ ],
23
+ };
11
24
  ```
12
25
 
13
- ## use
26
+ ### Payload 例子
27
+
28
+ header必须包含kid字段以匹配jwks中的密钥ID。
14
29
 
15
30
  ```ts
16
- import { App } from '@kevisual/router';
17
- import { createAuthRoute } from '@kevisual/auth';
18
-
19
- const app = new App();
20
- createAuthRoute({
21
- app: app,
22
- addToApp: true
23
- });
24
- app.listen(3000);
25
- // curl http://localhost:3000/api/router?path=auth
31
+ import * as jose from "jose";
32
+ // 加载测试私钥
33
+ const keys = JSON.parse(await Bun.file("./jwt/privateKey.json").text());
34
+ const privateKey = await jose.importJWK(keys, "RS256");
35
+
36
+ // 生成 RS256 JWT
37
+ const payload = {
38
+ iss: "https://convex.kevisual.cn",
39
+ sub: "user:8fa2be73c2229e85",
40
+ aud: "convex-app",
41
+ exp: Math.floor(Date.now() / 1000) + 3600,
42
+ name: "Test User AA",
43
+ email: "test@example.com",
44
+ };
45
+ const token = await new jose.SignJWT(payload)
46
+ .setProtectedHeader({
47
+ "alg": "RS256",
48
+ "typ": "JWT",
49
+ "kid": "kid-key-1"
50
+ })
51
+ .setIssuedAt()
52
+ .sign(privateKey);
26
53
  ```
package/src/auth.ts ADDED
@@ -0,0 +1,123 @@
1
+ import * as jose from 'jose';
2
+
3
+ /***
4
+ * 验证 JWT
5
+ * @param token JWT 字符串
6
+ * @param publicKey 公钥,可以是 CryptoKey、PEM 字符串或 JWK/JWKS 对象
7
+ * @returns 解码后的有效载荷
8
+ * @throws 如果验证失败或令牌无效,则抛出错误
9
+ */
10
+ export async function verifyJWT(token: string, publicKey: jose.CryptoKey | string | jose.JWK) {
11
+ try {
12
+ let key: any;
13
+ if (typeof publicKey === 'string') {
14
+ key = await jose.importSPKI(publicKey, 'RS256')
15
+ } else if (typeof publicKey === 'object' && publicKey !== null && 'keys' in publicKey) {
16
+ // JWKS 格式
17
+ key = jose.createLocalJWKSet(publicKey as jose.JSONWebKeySet);
18
+ } else {
19
+ key = publicKey;
20
+ }
21
+
22
+ const { payload } = await jose.jwtVerify(token, key, {
23
+ algorithms: ['RS256'],
24
+ });
25
+ return payload;
26
+ } catch (error) {
27
+ if (error instanceof jose.errors.JWTExpired) {
28
+ console.error('JWT has expired:');
29
+ throw new Error('Token has expired');
30
+ }
31
+ console.error('JWT verification failed:', error);
32
+ throw new Error('Invalid token');
33
+ }
34
+ }
35
+ export type JWTPayload<T = {}> = {
36
+ /**
37
+ * 会设置默认值: "https://convex.kevisual.cn"
38
+ */
39
+ iss?: string,
40
+ /**
41
+ * "user:8fa2be73c2229e85"
42
+ * "ip:192.168.1.1"
43
+ */
44
+ sub: string,
45
+ /**
46
+ * 会设置默认值:"convex-app"
47
+ */
48
+ aud?: string,
49
+ /**
50
+ * 会设置默认值:当前时间 + 2 小时
51
+ */
52
+ exp?: number,
53
+ /**
54
+ * 会设置默认值:当前时间
55
+ */
56
+ iat?: number,
57
+ /**
58
+ * 其他自定义字段
59
+ */
60
+ name?: string,
61
+ /**
62
+ * email
63
+ */
64
+ email?: string
65
+ } & T;
66
+
67
+ /***
68
+ * 签发 JWT
69
+ * @param payload 有效载荷
70
+ * @param privateKey 私钥,可以是 CryptoKey、PEM 字符串或 JWK 对象
71
+ * @returns 签发的 JWT 字符串
72
+ */
73
+ export async function signJWT(payload: JWTPayload, privateKey: jose.CryptoKey | string | jose.JWK): Promise<string> {
74
+ const expirationTime = Math.floor(Date.now() / 1000) + (2 * 60 * 60); // 2 hour from now
75
+ if (!payload.exp) {
76
+ payload.exp = expirationTime;
77
+ }
78
+ let cryptoKey: jose.CryptoKey;
79
+ if (typeof privateKey === 'string') {
80
+ cryptoKey = await jose.importPKCS8(privateKey, "RS256") as jose.CryptoKey;
81
+ } else if (typeof privateKey === 'object' && privateKey !== null && 'kty' in privateKey) {
82
+ cryptoKey = await jose.importJWK(privateKey, "RS256") as jose.CryptoKey;
83
+ } else {
84
+ cryptoKey = privateKey as jose.CryptoKey;
85
+ }
86
+ const iss = payload.iss || "https://convex.kevisual.cn";
87
+ if (!payload.iss) {
88
+ payload.iss = iss;
89
+ }
90
+ if (!payload.iat) {
91
+ payload.iat = Math.floor(Date.now() / 1000);
92
+ }
93
+ if (!payload.aud) {
94
+ payload.aud = "convex-app";
95
+ }
96
+
97
+ const token = await new jose.SignJWT(payload)
98
+ .setProtectedHeader({
99
+ "alg": "RS256",
100
+ "typ": "JWT",
101
+ "kid": "kid-key-1"
102
+ })
103
+ .setIssuedAt()
104
+ .setExpirationTime(payload.exp)
105
+ .setIssuer(iss)
106
+ .setSubject(payload.sub || "")
107
+ .sign(cryptoKey);
108
+ return token;
109
+ }
110
+
111
+ /**
112
+ * 单独解码 JWT,不验证签名
113
+ * @param token
114
+ * @returns
115
+ */
116
+ export const decodeJWT = (token: string): JWTPayload => {
117
+ try {
118
+ const decoded = jose.decodeJwt(token);
119
+ return decoded as JWTPayload;
120
+ } catch (error) {
121
+ throw new Error('Invalid token');
122
+ }
123
+ }
@@ -0,0 +1,39 @@
1
+ import * as jose from 'jose';
2
+
3
+ async function generateKeyPair() {
4
+ const { privateKey, publicKey } = await jose.generateKeyPair('RS256', {
5
+ modulusLength: 2048,
6
+ extractable: true,
7
+ });
8
+
9
+ return { privateKey, publicKey };
10
+ }
11
+
12
+ async function createJWKS(publicKey: CryptoKey, kid?: string) {
13
+ const jwk = await jose.exportJWK(publicKey);
14
+ // 添加 kid 字段
15
+ jwk.kid = kid || 'kid-key-1';
16
+ const jwks = {
17
+ keys: [jwk]
18
+ };
19
+ return jwks;
20
+ }
21
+
22
+ type GenerateOpts = {
23
+ kid?: string;
24
+ }
25
+ export const generate = async (opts: GenerateOpts = {}) => {
26
+ const { privateKey, publicKey } = await generateKeyPair();
27
+ const jwks = await createJWKS(publicKey, opts.kid);
28
+
29
+ // 将私钥和 JWKS 保存到文件
30
+ const privateJWK = await jose.exportJWK(privateKey);
31
+ const privatePEM = await jose.exportPKCS8(privateKey);
32
+ const publicPEM = await jose.exportSPKI(publicKey);
33
+ return {
34
+ jwks,
35
+ privateJWK,
36
+ privatePEM,
37
+ publicPEM
38
+ }
39
+ }
package/src/index.ts CHANGED
@@ -1,6 +1 @@
1
- import { getRandomSalt, cryptPwd, checkPwd } from './salt.ts';
2
- import { createToken, checkToken } from './create-token.ts';
3
-
4
- export { getRandomSalt, cryptPwd, checkPwd, createToken, checkToken };
5
-
6
- export { createAuthRoute } from './route.ts';
1
+ export * from './auth.ts';
@@ -0,0 +1,8 @@
1
+ import path from 'node:path';
2
+ const dir = path.join(process.cwd(), 'jwt');
3
+
4
+ export const JWKS_PATH = path.join(dir, 'jwks.json');
5
+ export const PRIVATE_JWK_PATH = path.join(dir, 'privateKey.json');
6
+
7
+ export const PRIVATE_KEY_PATH = path.join(dir, 'privateKey.txt');
8
+ export const PUBLIC_KEY_PATH = path.join(dir, 'publicKey.txt');
@@ -0,0 +1,23 @@
1
+
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import * as common from './common.ts';
5
+ import { generate } from '../generate.ts'
6
+
7
+ async function main() {
8
+ const { jwks, privateJWK, privatePEM, publicPEM } = await generate();
9
+ const dir = path.join(process.cwd(), 'jwt');
10
+ if (!fs.existsSync(dir)) {
11
+ fs.mkdirSync(dir);
12
+ }
13
+ fs.writeFileSync(common.PUBLIC_KEY_PATH, publicPEM);
14
+
15
+ fs.writeFileSync(common.PRIVATE_KEY_PATH, privatePEM);
16
+
17
+ fs.writeFileSync(common.PRIVATE_JWK_PATH, JSON.stringify(privateJWK, null, 2));
18
+ fs.writeFileSync(common.JWKS_PATH, JSON.stringify(jwks, null, 2));
19
+
20
+ console.log('Private key and JWKS have been saved to files.');
21
+ }
22
+
23
+ main().catch(console.error);
@@ -0,0 +1,22 @@
1
+
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import * as common from './common.ts';
5
+
6
+ export const getValues = () => {
7
+ if (!fs.existsSync(common.JWKS_PATH)) {
8
+ throw new Error('JWKS file does not exist. Please create it first.');
9
+ }
10
+ const jwksData = fs.readFileSync(common.JWKS_PATH, 'utf-8');
11
+ const privateJWKData = fs.readFileSync(common.PRIVATE_JWK_PATH, 'utf-8');
12
+ const publicKeyPEM = fs.readFileSync(common.PUBLIC_KEY_PATH, 'utf-8');
13
+ const privateKeyPEM = fs.readFileSync(common.PRIVATE_KEY_PATH, 'utf-8');
14
+
15
+ return {
16
+ jwks: JSON.parse(jwksData),
17
+ privateJWK: JSON.parse(privateJWKData),
18
+ publicKeyPEM,
19
+ privateKeyPEM
20
+ }
21
+ }
22
+
package/src/router.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { App } from '@kevisual/router';
2
+
package/test/create.ts ADDED
@@ -0,0 +1,38 @@
1
+ import * as jose from 'jose';
2
+ import { signJWT, decodeJWT, verifyJWT } from '../src/auth.ts';
3
+ import { getValues } from '../src/jwks/get.ts';
4
+
5
+ const payload = {
6
+ iss: "https://convex.kevisual.cn",
7
+ sub: "user:123456",
8
+ aud: "convex-app",
9
+ name: "John Doe",
10
+ email: "john.doe@example.com",
11
+ // exp: Math.floor(Date.now() / 1000) - (2 * 60 * 60) // 2 hours from now
12
+ }
13
+
14
+ const createToken = async () => {
15
+ const { privateJWK, privateKeyPEM } = getValues();
16
+ const token = await signJWT(payload, privateKeyPEM);
17
+ console.log('Generated JWT:', token);
18
+
19
+ // console.log('expited at:', new Date(payload.exp * 1000).toLocaleString());
20
+ return token;
21
+ }
22
+ const token = await createToken()
23
+
24
+ const decode = decodeJWT(token)
25
+
26
+ console.log('Decoded JWT:', decode);
27
+
28
+ const verify = async () => {
29
+ const { publicKeyPEM, privateJWK, jwks } = getValues();
30
+ try {
31
+ const verifiedPayload = await verifyJWT(token, publicKeyPEM);
32
+ console.log('Verified JWT payload:', verifiedPayload);
33
+ } catch (error) {
34
+ console.error('Verification failed:', error);
35
+ }
36
+ }
37
+
38
+ await verify();
@@ -1,31 +0,0 @@
1
- import jwt from 'jsonwebtoken';
2
-
3
- /**
4
- *
5
- * @param user
6
- * @param secret
7
- *
8
- * @param expiresIn default 7d expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d"
9
- * @returns
10
- */
11
- declare const createToken: (user: {
12
- id: string;
13
- username: string;
14
- [key: string]: any;
15
- }, secret: string, expiresIn?: string) => Promise<string>;
16
- /**
17
- * check token
18
- * @param token
19
- * @param secret
20
- * @returns
21
- */
22
- declare const checkToken: (token: string, secret: string) => Promise<jwt.Jwt>;
23
- /**
24
- * check auth and return token user
25
- * @param token
26
- * @param secret
27
- * @returns
28
- */
29
- declare const checkTokenUser: (token: string, secret: string) => Promise<string | jwt.JwtPayload>;
30
-
31
- export { checkToken, checkTokenUser, createToken };