@0xchain/token 1.1.0-beta.62 → 1.1.0-beta.63
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/dist/cookieStore.d.ts +25 -0
- package/dist/cookieStore.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -75
- package/dist/jwt.d.ts +10 -0
- package/dist/jwt.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie 唯一真相源。
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:domain / path / sameSite / secure 这套属性只在这里定义一次,
|
|
5
|
+
* set / get / remove 全部基于同一套属性。这样可以从机制上杜绝
|
|
6
|
+
* “写入带 secure、删除不带 secure 导致删不掉”这类属性漂移引发的
|
|
7
|
+
* cookie 时有时无问题。
|
|
8
|
+
*/
|
|
9
|
+
export declare const COOKIE_DOMAIN = ".ichatgo.ai";
|
|
10
|
+
export declare const COOKIE_PATH = "/";
|
|
11
|
+
export declare const COOKIE_SAME_SITE: "lax";
|
|
12
|
+
export declare const REFRESH_TOKEN_COOKIE_DAYS = 7;
|
|
13
|
+
export declare const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
|
|
14
|
+
/** js-cookie:number 表示「天数」,须用 Date 表示绝对过期时间 */
|
|
15
|
+
export declare function expiresFromSeconds(seconds: number): Date;
|
|
16
|
+
export declare const cookieStore: {
|
|
17
|
+
get(key: string): string;
|
|
18
|
+
set(key: string, value: string, expires: Date | number): void;
|
|
19
|
+
/**
|
|
20
|
+
* 删除必须与写入属性完全一致(domain/path/sameSite/secure),
|
|
21
|
+
* 否则浏览器无法命中目标 cookie,删除会“静默失败”。
|
|
22
|
+
*/
|
|
23
|
+
remove(key: string): void;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=cookieStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookieStore.d.ts","sourceRoot":"","sources":["../src/cookieStore.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAC1C,eAAO,MAAM,WAAW,MAAM,CAAA;AAC9B,eAAO,MAAM,gBAAgB,EAAG,KAAc,CAAA;AAE9C,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAC1C,eAAO,MAAM,gCAAgC,OAAO,CAAA;AAYpD,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,eAAO,MAAM,WAAW;aACb,MAAM,GAAG,MAAM;aAGf,MAAM,SAAS,MAAM,WAAW,IAAI,GAAG,MAAM,GAAG,IAAI;IAG7D;;;OAGG;gBACS,MAAM,GAAG,IAAI;CAG1B,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { ITokenManager, ITokenManagerResponse } from './type';
|
|
3
|
+
export { decodeJwtPayload, isJwtExpired } from './jwt';
|
|
3
4
|
/**
|
|
4
5
|
* 设置|更新token到cookie
|
|
5
6
|
* @param response NextResponse
|
|
@@ -11,6 +12,7 @@ export declare const getToken: (key?: string) => string;
|
|
|
11
12
|
/** 兼容 OAuth 直出与 { data: token } 包装 */
|
|
12
13
|
export declare function normalizeTokenPayload(res: unknown): ITokenManagerResponse | null;
|
|
13
14
|
export declare const removeAllToken: () => void;
|
|
15
|
+
export declare const removeTokenByKey: (key?: string) => void;
|
|
14
16
|
export declare const isTokenExpired: () => Promise<boolean>;
|
|
15
17
|
declare const TokenManager: ITokenManager;
|
|
16
18
|
export default TokenManager;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAa9D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AA2BvD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GACnB,KAAK,qBAAqB,EAC1B,WAAW,YAAY,GAAG,IAAI,KAC7B,YAAY,GAAG,IA0DjB,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,WAEpC,CAAC;AAEF,sCAAsC;AACtC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,GACX,qBAAqB,GAAG,IAAI,CAc9B;AAED,eAAO,MAAM,cAAc,YAM1B,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,SAE5C,CAAC;AAEF,eAAO,MAAM,cAAc,wBA6C1B,CAAC;AAEF,QAAA,MAAM,YAAY,EAAE,aAMnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,104 +14,147 @@ const COOKIE_DOMAIN = ".ichatgo.ai";
|
|
|
14
14
|
const COOKIE_PATH = "/";
|
|
15
15
|
const COOKIE_SAME_SITE = "lax";
|
|
16
16
|
const REFRESH_TOKEN_COOKIE_DAYS = 7;
|
|
17
|
+
const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
|
|
18
|
+
function baseAttributes() {
|
|
19
|
+
return {
|
|
20
|
+
domain: COOKIE_DOMAIN,
|
|
21
|
+
path: COOKIE_PATH,
|
|
22
|
+
sameSite: COOKIE_SAME_SITE,
|
|
23
|
+
secure: resolveCookieSecure()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
17
26
|
function expiresFromSeconds(seconds) {
|
|
18
27
|
return new Date(Date.now() + Math.max(seconds, 0) * 1e3);
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
const cookieStore = {
|
|
30
|
+
get(key) {
|
|
31
|
+
return Cookies.get(key) || "";
|
|
32
|
+
},
|
|
33
|
+
set(key, value, expires) {
|
|
34
|
+
Cookies.set(key, value, { ...baseAttributes(), expires });
|
|
35
|
+
},
|
|
36
|
+
/**
|
|
37
|
+
* 删除必须与写入属性完全一致(domain/path/sameSite/secure),
|
|
38
|
+
* 否则浏览器无法命中目标 cookie,删除会“静默失败”。
|
|
39
|
+
*/
|
|
40
|
+
remove(key) {
|
|
41
|
+
Cookies.remove(key, baseAttributes());
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function base64UrlDecode(input) {
|
|
45
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
46
|
+
const padLength = (4 - normalized.length % 4) % 4;
|
|
47
|
+
const padded = normalized + "=".repeat(padLength);
|
|
48
|
+
if (typeof atob === "function") {
|
|
49
|
+
const binary = atob(padded);
|
|
50
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
51
|
+
return new TextDecoder().decode(bytes);
|
|
52
|
+
}
|
|
53
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
54
|
+
}
|
|
55
|
+
function decodeJwtPayload(token) {
|
|
56
|
+
if (!token) return null;
|
|
22
57
|
const parts = token.split(".");
|
|
23
|
-
if (parts.length < 2) return
|
|
24
|
-
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
58
|
+
if (parts.length < 2) return null;
|
|
25
59
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const utf8 = decodeURIComponent(
|
|
29
|
-
raw.split("").map((c) => `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
30
|
-
);
|
|
31
|
-
const payload = JSON.parse(utf8);
|
|
32
|
-
if (typeof payload.exp === "number") {
|
|
33
|
-
return payload.exp * 1e3;
|
|
34
|
-
}
|
|
60
|
+
const payloadJson = base64UrlDecode(parts[1]);
|
|
61
|
+
return JSON.parse(payloadJson);
|
|
35
62
|
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getJwtExpiryMs(token) {
|
|
67
|
+
const payload = decodeJwtPayload(token);
|
|
68
|
+
if (typeof payload?.exp === "number") {
|
|
69
|
+
return payload.exp * 1e3;
|
|
36
70
|
}
|
|
37
71
|
return void 0;
|
|
38
72
|
}
|
|
73
|
+
function isJwtExpired(payload, options) {
|
|
74
|
+
if (!payload?.exp) return false;
|
|
75
|
+
const skew = options?.skewSeconds ?? 15;
|
|
76
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
77
|
+
return payload.exp <= now + skew;
|
|
78
|
+
}
|
|
39
79
|
const cookieSetToken = (data) => {
|
|
40
|
-
const
|
|
80
|
+
const nextAccessToken = data?.access_token || getToken("access_token");
|
|
81
|
+
const nextRefreshToken = data?.refresh_token || getToken("refresh_token");
|
|
82
|
+
const nextIdToken = data?.id_token || getToken("id_token");
|
|
83
|
+
if (!nextAccessToken) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const expiresIn = data?.expires_in ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
|
|
41
87
|
const accessExpires = expiresFromSeconds(expiresIn);
|
|
88
|
+
cookieStore.set("access_token", nextAccessToken, accessExpires);
|
|
89
|
+
if (nextRefreshToken) {
|
|
90
|
+
cookieStore.set("refresh_token", nextRefreshToken, REFRESH_TOKEN_COOKIE_DAYS);
|
|
91
|
+
}
|
|
92
|
+
if (nextIdToken) {
|
|
93
|
+
cookieStore.set("id_token", nextIdToken, accessExpires);
|
|
94
|
+
}
|
|
95
|
+
cookieStore.set(
|
|
96
|
+
"token_expire",
|
|
97
|
+
(Date.now() + expiresIn * 1e3).toString(),
|
|
98
|
+
accessExpires
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
const setToken = (res, response) => {
|
|
102
|
+
if (!response) {
|
|
103
|
+
cookieSetToken(res);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const nextAccessToken = res?.access_token;
|
|
107
|
+
const nextRefreshToken = res?.refresh_token;
|
|
108
|
+
const nextIdToken = res?.id_token;
|
|
109
|
+
const expiresIn = typeof res?.expires_in === "number" && res.expires_in > 0 ? res.expires_in : DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
|
|
110
|
+
if (!nextAccessToken) {
|
|
111
|
+
return response;
|
|
112
|
+
}
|
|
42
113
|
const secure = resolveCookieSecure();
|
|
43
|
-
|
|
44
|
-
|
|
114
|
+
response.cookies.set("access_token", nextAccessToken, {
|
|
115
|
+
httpOnly: false,
|
|
116
|
+
maxAge: expiresIn,
|
|
45
117
|
domain: COOKIE_DOMAIN,
|
|
46
118
|
sameSite: COOKIE_SAME_SITE,
|
|
47
119
|
secure,
|
|
48
120
|
path: COOKIE_PATH
|
|
49
121
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
domain: COOKIE_DOMAIN,
|
|
53
|
-
sameSite: COOKIE_SAME_SITE,
|
|
54
|
-
secure,
|
|
55
|
-
path: COOKIE_PATH
|
|
56
|
-
});
|
|
57
|
-
Cookies.set("id_token", data?.id_token, {
|
|
58
|
-
expires: accessExpires,
|
|
59
|
-
domain: COOKIE_DOMAIN,
|
|
60
|
-
sameSite: COOKIE_SAME_SITE,
|
|
61
|
-
secure,
|
|
62
|
-
path: COOKIE_PATH
|
|
63
|
-
});
|
|
64
|
-
Cookies.set("token_expire", (Date.now() + expiresIn * 1e3).toString(), {
|
|
65
|
-
expires: accessExpires,
|
|
66
|
-
domain: COOKIE_DOMAIN,
|
|
67
|
-
sameSite: COOKIE_SAME_SITE,
|
|
68
|
-
secure,
|
|
69
|
-
path: COOKIE_PATH
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
const setToken = (res, response) => {
|
|
73
|
-
if (!response) {
|
|
74
|
-
cookieSetToken(res);
|
|
75
|
-
return null;
|
|
76
|
-
} else {
|
|
77
|
-
const secure = resolveCookieSecure();
|
|
78
|
-
response.cookies.set("access_token", res?.access_token, {
|
|
79
|
-
httpOnly: false,
|
|
80
|
-
maxAge: res?.expires_in,
|
|
81
|
-
domain: COOKIE_DOMAIN,
|
|
82
|
-
sameSite: COOKIE_SAME_SITE,
|
|
83
|
-
secure,
|
|
84
|
-
path: COOKIE_PATH
|
|
85
|
-
});
|
|
86
|
-
response.cookies.set("refresh_token", res?.refresh_token, {
|
|
122
|
+
if (nextRefreshToken) {
|
|
123
|
+
response.cookies.set("refresh_token", nextRefreshToken, {
|
|
87
124
|
httpOnly: false,
|
|
88
|
-
maxAge: 60 * 60 * 24 *
|
|
125
|
+
maxAge: 60 * 60 * 24 * REFRESH_TOKEN_COOKIE_DAYS,
|
|
89
126
|
domain: COOKIE_DOMAIN,
|
|
90
127
|
sameSite: COOKIE_SAME_SITE,
|
|
91
128
|
secure,
|
|
92
129
|
path: COOKIE_PATH
|
|
93
130
|
});
|
|
94
|
-
|
|
131
|
+
}
|
|
132
|
+
if (nextIdToken) {
|
|
133
|
+
response.cookies.set("id_token", nextIdToken, {
|
|
95
134
|
httpOnly: false,
|
|
96
|
-
maxAge:
|
|
135
|
+
maxAge: expiresIn,
|
|
97
136
|
domain: COOKIE_DOMAIN,
|
|
98
137
|
sameSite: COOKIE_SAME_SITE,
|
|
99
138
|
secure,
|
|
100
139
|
path: COOKIE_PATH
|
|
101
140
|
});
|
|
102
|
-
|
|
141
|
+
}
|
|
142
|
+
response.cookies.set(
|
|
143
|
+
"token_expire",
|
|
144
|
+
(Date.now() + expiresIn * 1e3).toString(),
|
|
145
|
+
{
|
|
103
146
|
httpOnly: false,
|
|
104
|
-
maxAge:
|
|
147
|
+
maxAge: expiresIn,
|
|
105
148
|
domain: COOKIE_DOMAIN,
|
|
106
149
|
sameSite: COOKIE_SAME_SITE,
|
|
107
150
|
secure,
|
|
108
151
|
path: COOKIE_PATH
|
|
109
|
-
}
|
|
110
|
-
|
|
152
|
+
}
|
|
153
|
+
);
|
|
111
154
|
return response;
|
|
112
155
|
};
|
|
113
156
|
const getToken = (key) => {
|
|
114
|
-
return
|
|
157
|
+
return cookieStore.get(key || "id_token");
|
|
115
158
|
};
|
|
116
159
|
function normalizeTokenPayload(res) {
|
|
117
160
|
if (!res || typeof res !== "object") return null;
|
|
@@ -129,14 +172,14 @@ function normalizeTokenPayload(res) {
|
|
|
129
172
|
return null;
|
|
130
173
|
}
|
|
131
174
|
const removeAllToken = () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
175
|
+
cookieStore.remove("id_token");
|
|
176
|
+
cookieStore.remove("access_token");
|
|
177
|
+
cookieStore.remove("refresh_token");
|
|
178
|
+
cookieStore.remove("sign_session");
|
|
179
|
+
cookieStore.remove("token_expire");
|
|
137
180
|
};
|
|
138
181
|
const removeTokenByKey = (key) => {
|
|
139
|
-
|
|
182
|
+
cookieStore.remove(key || "id_token");
|
|
140
183
|
};
|
|
141
184
|
const isTokenExpired = async () => {
|
|
142
185
|
let t;
|
|
@@ -145,23 +188,23 @@ const isTokenExpired = async () => {
|
|
|
145
188
|
if (typeof window === "undefined") {
|
|
146
189
|
try {
|
|
147
190
|
const { cookies } = await import("next/headers");
|
|
148
|
-
const
|
|
149
|
-
const tokenExpire =
|
|
150
|
-
refreshToken =
|
|
151
|
-
accessToken =
|
|
191
|
+
const nextCookies = await cookies();
|
|
192
|
+
const tokenExpire = nextCookies.get("token_expire")?.value;
|
|
193
|
+
refreshToken = nextCookies.get("refresh_token")?.value;
|
|
194
|
+
accessToken = nextCookies.get("access_token")?.value;
|
|
152
195
|
t = tokenExpire ? Number(tokenExpire) : void 0;
|
|
153
196
|
} catch {
|
|
154
197
|
return false;
|
|
155
198
|
}
|
|
156
199
|
} else {
|
|
157
|
-
const rawExpire =
|
|
200
|
+
const rawExpire = cookieStore.get("token_expire");
|
|
158
201
|
t = rawExpire ? Number(rawExpire) : void 0;
|
|
159
|
-
refreshToken =
|
|
160
|
-
accessToken =
|
|
202
|
+
refreshToken = cookieStore.get("refresh_token");
|
|
203
|
+
accessToken = cookieStore.get("access_token");
|
|
161
204
|
}
|
|
162
205
|
const nearExpiry = (expireAt) => expireAt - Date.now() < 5 * 60 * 1e3;
|
|
163
206
|
if (accessToken) {
|
|
164
|
-
const jwtExpiry =
|
|
207
|
+
const jwtExpiry = getJwtExpiryMs(accessToken);
|
|
165
208
|
if (jwtExpiry) {
|
|
166
209
|
return nearExpiry(jwtExpiry);
|
|
167
210
|
}
|
|
@@ -186,10 +229,13 @@ const TokenManager = {
|
|
|
186
229
|
isTokenExpired
|
|
187
230
|
};
|
|
188
231
|
export {
|
|
232
|
+
decodeJwtPayload,
|
|
189
233
|
TokenManager as default,
|
|
190
234
|
getToken,
|
|
235
|
+
isJwtExpired,
|
|
191
236
|
isTokenExpired,
|
|
192
237
|
normalizeTokenPayload,
|
|
193
238
|
removeAllToken,
|
|
239
|
+
removeTokenByKey,
|
|
194
240
|
setToken
|
|
195
241
|
};
|
package/dist/jwt.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type JwtPayload = Record<string, unknown> & {
|
|
2
|
+
exp?: number;
|
|
3
|
+
};
|
|
4
|
+
export declare function decodeJwtPayload(token?: string | null): JwtPayload | null;
|
|
5
|
+
/** JWT exp 字段转毫秒时间戳;无法解析时返回 undefined */
|
|
6
|
+
export declare function getJwtExpiryMs(token?: string | null): number | undefined;
|
|
7
|
+
export declare function isJwtExpired(payload: JwtPayload | null, options?: {
|
|
8
|
+
skewSeconds?: number;
|
|
9
|
+
}): boolean;
|
|
10
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAUzE;AAED,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMxE;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,GAAG,IAAI,EAC1B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAKT"}
|