@longzai-intelligence-auth/elysia 0.0.6 → 0.0.8
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/index.d.ts +182 -2
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,177 @@ type StaticTokenVerifierOptions = {
|
|
|
45
45
|
*/
|
|
46
46
|
declare function createStaticTokenVerifier(options: StaticTokenVerifierOptions): TokenVerifierPort;
|
|
47
47
|
//#endregion
|
|
48
|
+
//#region src/extractors/types.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* 凭证提取器类型定义
|
|
51
|
+
*
|
|
52
|
+
* 提取器负责从 Request 中提取凭证字符串,与"如何验证凭证"完全解耦。
|
|
53
|
+
* 同一种提取器(如 cookie)可以配合任意验证器(JWT、静态 token、远程校验等)。
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* 凭证提取器接口
|
|
57
|
+
*
|
|
58
|
+
* 一个提取器回答一个问题:这个请求里有没有携带凭证,如果有,凭证字符串是什么?
|
|
59
|
+
* 提取器不做任何验证,仅负责"取出"。
|
|
60
|
+
*/
|
|
61
|
+
type CredentialExtractor = {
|
|
62
|
+
/**
|
|
63
|
+
* 从请求中提取凭证字符串
|
|
64
|
+
*
|
|
65
|
+
* @param request - Web 标准 Request 对象
|
|
66
|
+
* @returns 凭证字符串;请求未携带凭证时返回 null
|
|
67
|
+
*/
|
|
68
|
+
extract(request: Request): string | null;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* 请求级令牌验证器接口
|
|
72
|
+
*
|
|
73
|
+
* 与 TokenVerifierPort(输入字符串)解耦:RequestTokenVerifier 接受 Request,
|
|
74
|
+
* 内部组合"提取器 + 令牌验证器",对调用方屏蔽凭证载体细节。
|
|
75
|
+
*
|
|
76
|
+
* 用于在 plugin 层组合多种载体(cookie、bearer、api-key header)与多种算法(JWT、静态比较)。
|
|
77
|
+
*/
|
|
78
|
+
type RequestTokenVerifier = {
|
|
79
|
+
/**
|
|
80
|
+
* 验证请求中的凭证
|
|
81
|
+
*
|
|
82
|
+
* @param request - Web 标准 Request 对象
|
|
83
|
+
* @returns 验证结果,含 AccessTokenPayload 或错误信息
|
|
84
|
+
*/
|
|
85
|
+
verify(request: Request): Promise<import("@longzai-intelligence-auth/core").TokenVerifyResult<import("@longzai-intelligence-auth/core").AccessTokenPayload>>;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/extractors/cookie-extractor.d.ts
|
|
89
|
+
/**
|
|
90
|
+
* Cookie 提取器配置
|
|
91
|
+
*/
|
|
92
|
+
type CookieExtractorOptions = {
|
|
93
|
+
/**
|
|
94
|
+
* Cookie 名称(必填)
|
|
95
|
+
*/
|
|
96
|
+
cookieName: string;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* 创建 Cookie 凭证提取器
|
|
100
|
+
*
|
|
101
|
+
* 兼容 Elysia/Bun 的 cookie 解析行为:仅做简单的"key=value"拆分,
|
|
102
|
+
* 不处理属性(Domain/Path/Expires/Secure/HttpOnly/SameSite 等)。
|
|
103
|
+
* 复杂场景下建议消费方自行解析后通过其他 extractor 注入。
|
|
104
|
+
*
|
|
105
|
+
* @param options - Cookie 提取器配置
|
|
106
|
+
* @returns 实现 CredentialExtractor 的 cookie 提取器
|
|
107
|
+
*/
|
|
108
|
+
declare function createCookieExtractor(options: CookieExtractorOptions): CredentialExtractor;
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/extractors/bearer-extractor.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Bearer 提取器配置
|
|
113
|
+
*/
|
|
114
|
+
type BearerExtractorOptions = {
|
|
115
|
+
/**
|
|
116
|
+
* Authorization 头字段名(默认 "authorization")
|
|
117
|
+
* 大小写不敏感(HTTP header 字段名本身大小写不敏感)
|
|
118
|
+
*/
|
|
119
|
+
headerName?: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* 创建 Bearer 凭证提取器
|
|
123
|
+
*
|
|
124
|
+
* 兼容标准 "Authorization: Bearer <token>" 格式,
|
|
125
|
+
* 不做任何令牌格式校验(由 verifier 负责)。
|
|
126
|
+
*
|
|
127
|
+
* @param options - Bearer 提取器配置
|
|
128
|
+
* @returns 实现 CredentialExtractor 的 bearer 提取器
|
|
129
|
+
*/
|
|
130
|
+
declare function createBearerExtractor(options?: BearerExtractorOptions): CredentialExtractor;
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/extractors/api-key-extractor.d.ts
|
|
133
|
+
/**
|
|
134
|
+
* API Key 提取器配置
|
|
135
|
+
*/
|
|
136
|
+
type ApiKeyExtractorOptions = {
|
|
137
|
+
/**
|
|
138
|
+
* 头字段名(默认 "x-api-key")
|
|
139
|
+
* 大小写不敏感:先精确匹配,再尝试小写形式(HTTP header 字段名本身大小写不敏感)
|
|
140
|
+
*/
|
|
141
|
+
headerName?: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* 创建 API Key 凭证提取器
|
|
145
|
+
*
|
|
146
|
+
* 不做任何令牌格式校验(由 verifier 负责)。
|
|
147
|
+
*
|
|
148
|
+
* @param options - API Key 提取器配置
|
|
149
|
+
* @returns 实现 CredentialExtractor 的 api-key 提取器
|
|
150
|
+
*/
|
|
151
|
+
declare function createApiKeyExtractor(options?: ApiKeyExtractorOptions): CredentialExtractor;
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/verifiers/cookie-jwt-verifier.d.ts
|
|
154
|
+
/**
|
|
155
|
+
* Cookie JWT 验证器配置
|
|
156
|
+
*/
|
|
157
|
+
type CookieJwtVerifierOptions = {
|
|
158
|
+
/**
|
|
159
|
+
* Cookie 名称(承载 JWT 的 cookie 字段名)
|
|
160
|
+
*/
|
|
161
|
+
cookieName: string;
|
|
162
|
+
/**
|
|
163
|
+
* JWT 配置(与 verifier 二选一)
|
|
164
|
+
* 提供时内部创建 JwtTokenVerifier 实例
|
|
165
|
+
*/
|
|
166
|
+
jwt?: JwtConfig;
|
|
167
|
+
/**
|
|
168
|
+
* 已构造的 TokenVerifierPort(与 jwt 二选一)
|
|
169
|
+
* 用于注入自定义 verifier(如多策略、远程校验等)
|
|
170
|
+
*/
|
|
171
|
+
verifier?: TokenVerifierPort;
|
|
172
|
+
/**
|
|
173
|
+
* 自定义凭证提取器(高级用法)
|
|
174
|
+
* 默认使用 createCookieExtractor({ cookieName })
|
|
175
|
+
*/
|
|
176
|
+
extractor?: CredentialExtractor;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* 创建 Cookie JWT 验证器
|
|
180
|
+
*
|
|
181
|
+
* @param options - Cookie JWT 验证器配置
|
|
182
|
+
* @returns 实现 RequestTokenVerifier 的组合验证器
|
|
183
|
+
*
|
|
184
|
+
* @throws {@link Error} 当 verifier 和 jwt 都未提供时
|
|
185
|
+
*/
|
|
186
|
+
declare function createCookieJwtVerifier(options: CookieJwtVerifierOptions): RequestTokenVerifier;
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/verifiers/bearer-jwt-verifier.d.ts
|
|
189
|
+
/**
|
|
190
|
+
* Bearer JWT 验证器配置
|
|
191
|
+
*/
|
|
192
|
+
type BearerJwtVerifierOptions = {
|
|
193
|
+
/**
|
|
194
|
+
* JWT 配置(与 verifier 二选一)
|
|
195
|
+
* 提供时内部创建 JwtTokenVerifier 实例
|
|
196
|
+
*/
|
|
197
|
+
jwt?: JwtConfig;
|
|
198
|
+
/**
|
|
199
|
+
* 已构造的 TokenVerifierPort(与 jwt 二选一)
|
|
200
|
+
* 用于注入自定义 verifier(如多策略、远程校验等)
|
|
201
|
+
*/
|
|
202
|
+
verifier?: TokenVerifierPort;
|
|
203
|
+
/**
|
|
204
|
+
* 自定义凭证提取器(高级用法)
|
|
205
|
+
* 默认使用 createBearerExtractor()(标准 Authorization: Bearer 头)
|
|
206
|
+
*/
|
|
207
|
+
extractor?: CredentialExtractor;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* 创建 Bearer JWT 验证器
|
|
211
|
+
*
|
|
212
|
+
* @param options - Bearer JWT 验证器配置
|
|
213
|
+
* @returns 实现 RequestTokenVerifier 的组合验证器
|
|
214
|
+
*
|
|
215
|
+
* @throws {@link Error} 当 verifier 和 jwt 都未提供时
|
|
216
|
+
*/
|
|
217
|
+
declare function createBearerJwtVerifier(options: BearerJwtVerifierOptions): RequestTokenVerifier;
|
|
218
|
+
//#endregion
|
|
48
219
|
//#region src/plugins/jwt-verify.plugin.d.ts
|
|
49
220
|
/** JWT 验证插件解析后的认证上下文 */
|
|
50
221
|
type VerifiedAuthContext = {
|
|
@@ -392,10 +563,19 @@ declare function createInMemoryApiKeyValidator(keyMap: InMemoryApiKeyMap): (key:
|
|
|
392
563
|
type CompositeAuthPluginOptions = {
|
|
393
564
|
/**
|
|
394
565
|
* 令牌验证器端口(用户注入,与 jwt 二选一)
|
|
566
|
+
*
|
|
567
|
+
* 单个验证器场景的简写形式;与 verifiers 同时提供时会合并到列表头部。
|
|
395
568
|
*/
|
|
396
569
|
verifier?: TokenVerifierPort;
|
|
397
570
|
/**
|
|
398
|
-
*
|
|
571
|
+
* 令牌验证器端口列表(多验证器场景)
|
|
572
|
+
*
|
|
573
|
+
* 按数组顺序依次尝试,任一成功即返回;
|
|
574
|
+
* 用于多模式共存(如同时支持 cookie JWT 与静态令牌、新旧密钥平滑迁移等)。
|
|
575
|
+
*/
|
|
576
|
+
verifiers?: TokenVerifierPort[];
|
|
577
|
+
/**
|
|
578
|
+
* JWT 配置(与 verifier/verifiers 二选一,未提供时使用默认配置)
|
|
399
579
|
*/
|
|
400
580
|
jwt?: JwtConfig;
|
|
401
581
|
/**
|
|
@@ -896,4 +1076,4 @@ declare function extractClientIp(request: Request): string | undefined;
|
|
|
896
1076
|
*/
|
|
897
1077
|
declare function extractUserAgent(request: Request): string | undefined;
|
|
898
1078
|
//#endregion
|
|
899
|
-
export { type ApiKeyPluginOptions, type ApiKeyPrincipal, type AuthContextWithExtra, type BasicPresetOptions, type CompositeAuthContext, type CompositeAuthPluginOptions, type ErrorHandlerPluginOptions, type ExtractExtra, type InMemoryApiKeyEntry, type InMemoryApiKeyMap, type JwtVerifyPluginOptions, type LoggerPluginOptions, type OptionalAuthContext, type OptionalJwtPluginOptions, type RateLimitPluginOptions, type RbacPluginOptions, type StandardPresetOptions, type StaticTokenVerifierOptions, type UnifiedAuthContext, type UserRoleStrategy, type VerifiedAuthContext, createApiKeyPlugin, createBasicPreset, createCompositeAuthPlugin, createErrorHandlerPlugin, createInMemoryApiKeyValidator, createJwtVerifyPlugin, createLoggerPlugin, createOptionalJwtPlugin, createRateLimitPlugin, createRbacPlugin, createRequiredCompositeAuthPlugin, createStandardPreset, createStaticTokenVerifier, extractClientIp, extractUserAgent, mapDomainErrorToStatus };
|
|
1079
|
+
export { type ApiKeyExtractorOptions, type ApiKeyPluginOptions, type ApiKeyPrincipal, type AuthContextWithExtra, type BasicPresetOptions, type BearerExtractorOptions, type BearerJwtVerifierOptions, type CompositeAuthContext, type CompositeAuthPluginOptions, type CookieExtractorOptions, type CookieJwtVerifierOptions, type CredentialExtractor, type ErrorHandlerPluginOptions, type ExtractExtra, type InMemoryApiKeyEntry, type InMemoryApiKeyMap, type JwtVerifyPluginOptions, type LoggerPluginOptions, type OptionalAuthContext, type OptionalJwtPluginOptions, type RateLimitPluginOptions, type RbacPluginOptions, type RequestTokenVerifier, type StandardPresetOptions, type StaticTokenVerifierOptions, type UnifiedAuthContext, type UserRoleStrategy, type VerifiedAuthContext, createApiKeyExtractor, createApiKeyPlugin, createBasicPreset, createBearerExtractor, createBearerJwtVerifier, createCompositeAuthPlugin, createCookieExtractor, createCookieJwtVerifier, createErrorHandlerPlugin, createInMemoryApiKeyValidator, createJwtVerifyPlugin, createLoggerPlugin, createOptionalJwtPlugin, createRateLimitPlugin, createRbacPlugin, createRequiredCompositeAuthPlugin, createStandardPreset, createStaticTokenVerifier, extractClientIp, extractUserAgent, mapDomainErrorToStatus };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuthenticationError as e,DomainError as t,DuplicateEntityError as n,isBusinessRuleError as r,isConcurrencyError as i,isEntityNotFoundError as a,isPermissionDeniedError as o,isValidationError as s}from"@longzai-intelligence/error";import{AccountDisabledError as c,InvalidCredentialsError as l,MfaRequiredError as u,RateLimitExceededError as d,TokenExpiredError as f,TokenInvalidError as p,TokenMissingError as m,UserAlreadyExistsError as h,accessTokenPayloadSchema as g,createDefaultJwtConfig as _}from"@longzai-intelligence-auth/core";import v from"node:crypto";import{Elysia as y}from"elysia";import{JwtTokenVerifier as b}from"@longzai-intelligence-auth/jwt";function x(t){return s(t)?400:t instanceof f||t instanceof p||t instanceof m||t instanceof u||t instanceof l||t instanceof c||t instanceof e?401:o(t)?403:a(t)?404:t instanceof n||t instanceof h?409:t instanceof d?429:i(t)?409:r(t)||t.code===`PASSWORD_POLICY_VIOLATION`?422:500}function S(e,t){let n=Buffer.from(e,`utf-8`),r=Buffer.from(t,`utf-8`);if(n.length!==r.length){let e=Buffer.alloc(n.length);return v.timingSafeEqual(n,e),!1}return v.timingSafeEqual(n,r)}function C(e){let{token:t,getToken:n,subject:r=`static`,tenantId:i}=e;if(!t&&!n)throw Error(`createStaticTokenVerifier 需要至少提供 token 或 getToken 之一`);let a=()=>{if(n){let e=n();if(e)return e}return t??null};return{async verifyAccessToken(e){let t=a();return t?S(e,t)?{success:!0,payload:{sub:r,type:`access`,...i!==void 0&&{tenantId:i}}}:{success:!1,error:`静态令牌不匹配`}:{success:!1,error:`静态令牌未配置`}},async verifyRefreshToken(){return{success:!1,error:`静态令牌模式不支持 refresh token`}}}}function w(e,t){return{userId:e.sub,tenantId:e.tenantId??t,roles:e.roles??[],permissions:e.permissions??[],authMethod:`jwt`}}function T(e){return{userId:``,tenantId:e,roles:[],permissions:[],authMethod:`jwt`}}function E(e,t){return t.some(t=>t.endsWith(`/*`)?e.startsWith(t.slice(0,-2)):e===t)}function D(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function O(e,t){return e||new b(D(t??_()))}function k(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,publicPaths:i=[],extendSchema:a}=e,o=O(t,n);return new y({name:`@longzai-intelligence-auth/jwt-verify`}).derive(async({request:e})=>{if(E(new URL(e.url).pathname,i)){let e=a?a.parse({}):{};return{auth:{...T(r),...e}}}let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(!n)throw new m;let s=await o.verifyAccessToken(n);if(!s.success||!s.payload)throw new p(s.error);g.parse(s.payload);let c=w(s.payload,r),l=a?a.parse(s.payload):{};return{auth:{...c,...l}}}).as(`scoped`)}function A(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function j(e,t){return e||new b(A(t??_()))}function M(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,extendSchema:i}=e,a=j(t,n);return new y({name:`@longzai-intelligence-auth/optional-jwt`}).derive(async({request:e})=>{let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(!n)return{auth:null};try{let e=await a.verifyAccessToken(n);if(!e.success||!e.payload)return{auth:null};g.parse(e.payload);let t=e.payload,o={userId:t.sub,tenantId:t.tenantId??r,roles:t.roles??[],permissions:t.permissions??[],authMethod:`jwt`},s=i?i.parse(t):{};return{auth:{...o,...s}}}catch{return{auth:null}}}).as(`scoped`)}function N(e,t,n){return e.some(e=>e.resource===`*`&&e.action===`*`||e.resource===t&&e.action===`*`||e.resource===`*`&&e.action===n||e.resource===t&&e.action===n)}function P(e,t,n){let r=`${t}:${n}`;return e.includes(r)||e.includes(`${t}:*`)||e.includes(`*:${n}`)||e.includes(`*:*`)}function F(e={}){let{userRole:t}=e;return new y({name:`@longzai-intelligence-auth/rbac`}).derive({as:`scoped`},async e=>{let n=e.auth;if(!n)throw new m;let r=n.userId,i=n.tenantId;return{requirePermission:async(e,a)=>{if(t){if(N(await t.findPermissionsByUserId(r,i),e,a))return;let{PermissionDeniedError:n}=await import(`@longzai-intelligence/error`);throw new n(e,a)}if(P(n.permissions??[],e,a))return;let{PermissionDeniedError:o}=await import(`@longzai-intelligence/error`);throw new o(e,a)}}}).as(`global`)}function I(e){let{header:t=`x-api-key`,validator:n}=e;return new y({name:`@longzai-intelligence-auth/api-key`}).derive(async({request:e})=>{let r=e.headers.get(t)??e.headers.get(t.toLowerCase());if(!r)throw new m(`缺少 API Key`);let i=await n(r);if(!i)throw new p(`无效的 API Key`);return{auth:{userId:i.principalId,tenantId:i.tenantId??`default`,roles:i.roles,permissions:i.permissions,authMethod:`api-key`}}}).as(`scoped`)}function L(e){return async t=>{let n=e[t];return n?{principalId:n.principalId,tenantId:n.tenantId,roles:n.roles,permissions:n.permissions}:null}}function R(e){return{authMethod:`jwt`,userId:e.userId,tenantId:e.tenantId,roles:e.roles,permissions:e.permissions}}function z(e,t){return{authMethod:`api-key`,userId:e.principalId,tenantId:e.tenantId??t,roles:e.roles,permissions:e.permissions}}function B(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function V(e,t){return e||new b(B(t??_()))}function H(e,t){return t.some(t=>t.endsWith(`/*`)?e.startsWith(t.slice(0,-2)):e===t)}function U(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,apiKeyValidator:i,apiKeyHeader:a=`x-api-key`,publicPaths:o=[]}=e,s=V(t,n);return new y({name:`@longzai-intelligence-auth/composite-auth`}).derive(async({request:e})=>{if(H(new URL(e.url).pathname,o))return{auth:null};let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(n)try{let e=await s.verifyAccessToken(n);if(e.success&&e.payload)return g.parse(e.payload),{auth:R({userId:e.payload.sub,tenantId:e.payload.tenantId??r,roles:e.payload.roles??[],permissions:e.payload.permissions??[]})}}catch{}if(i){let t=e.headers.get(a)??e.headers.get(a.toLowerCase());if(t){let e=await i(t);if(e)return{auth:z(e,r)}}}return{auth:null}}).as(`scoped`)}function W(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,apiKeyValidator:i,apiKeyHeader:a=`x-api-key`,publicPaths:o=[]}=e,s=V(t,n);return new y({name:`@longzai-intelligence-auth/required-composite-auth`}).derive(async({request:e})=>{if(H(new URL(e.url).pathname,o))return{auth:{authMethod:`jwt`,userId:``,tenantId:r,roles:[],permissions:[]}};let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(n)try{let e=await s.verifyAccessToken(n);if(e.success&&e.payload)return g.parse(e.payload),{auth:R({userId:e.payload.sub,tenantId:e.payload.tenantId??r,roles:e.payload.roles??[],permissions:e.payload.permissions??[]})}}catch{}if(i){let t=e.headers.get(a)??e.headers.get(a.toLowerCase());if(t){let e=await i(t);if(e)return{auth:z(e,r)}}}throw Error(`认证失败:需要提供有效的 JWT Token 或 API Key`)}).as(`scoped`)}function G(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??`unknown`}function K(e){let{limiter:t,logger:n}=e,r=e.keyGenerator??G;return new y({name:`@longzai-intelligence-auth/rate-limit`}).derive(({request:e})=>({clientIp:r(e)})).derive(async({clientIp:e,request:r})=>{let i=new URL(r.url).pathname,a=`${e}:${i}`,o=await t.checkLimit(a);if(!o.allowed)throw n?.warn(`限流触发`,{clientIp:e,path:i}),new d;return{rateLimitRemaining:o.remaining}}).as(`global`)}function q(e={}){let{logger:n}=e;return new y({name:`@longzai-intelligence-auth/error-handler`}).onError(({code:e,error:r,set:i})=>{if(r instanceof t)return i.status=x(r),{code:r.code,message:r.message,...r.context&&Object.keys(r.context).length>0?{details:r.context}:{}};switch(e){case`VALIDATION`:return i.status=400,{code:`VALIDATION_ERROR`,message:`请求参数验证失败`,details:r.all.map(e=>({path:e.path,message:e.summary}))};case`NOT_FOUND`:return i.status=404,{code:`NOT_FOUND`,message:`未找到请求的资源`};case`PARSE`:return i.status=400,{code:`PARSE_ERROR`,message:`请求数据解析失败`};default:return n?.error(`未预期的错误`,{error:r instanceof Error?r.message:String(r),stack:r instanceof Error?r.stack:void 0}),i.status=500,{code:`INTERNAL_ERROR`,message:`服务器内部错误`}}}).as(`global`)}function J(e){let{logger:t}=e;return new y({name:`@longzai-intelligence-auth/logger`}).onRequest(({request:e})=>{t.info(`${e.method} ${e.url}`)}).onAfterResponse(({request:e,set:n})=>{t.info(`${e.method} ${e.url} ${n.status}`)}).onError(({request:e,error:n})=>{t.error(`${e.method} ${e.url} 请求错误`,{error:n instanceof Error?n.message:String(n)})}).as(`global`)}function Y(e){return new y({name:`@longzai-intelligence-auth/basic-preset`}).use(k({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,extendSchema:e.extendSchema})).use(q({logger:e.logger}))}function X(e){let t=e.apiKeyValidator?W({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,apiKeyValidator:e.apiKeyValidator}):k({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,extendSchema:e.extendSchema}),n=new y({name:`@longzai-intelligence-auth/standard-preset`}).use(t).use(F({userRole:e.userRole}));return e.rateLimiter&&n.use(K({limiter:e.rateLimiter,logger:e.logger})),n.use(q({logger:e.logger}))}function Z(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??void 0}function Q(e){return e.headers.get(`user-agent`)??void 0}export{I as createApiKeyPlugin,Y as createBasicPreset,U as createCompositeAuthPlugin,q as createErrorHandlerPlugin,L as createInMemoryApiKeyValidator,k as createJwtVerifyPlugin,J as createLoggerPlugin,M as createOptionalJwtPlugin,K as createRateLimitPlugin,F as createRbacPlugin,W as createRequiredCompositeAuthPlugin,X as createStandardPreset,C as createStaticTokenVerifier,Z as extractClientIp,Q as extractUserAgent,x as mapDomainErrorToStatus};
|
|
1
|
+
import{AuthenticationError as e,DomainError as t,DuplicateEntityError as n,isBusinessRuleError as r,isConcurrencyError as i,isEntityNotFoundError as a,isPermissionDeniedError as o,isValidationError as s}from"@longzai-intelligence/error";import{AccountDisabledError as c,InvalidCredentialsError as l,MfaRequiredError as u,RateLimitExceededError as d,TokenExpiredError as ee,TokenInvalidError as f,TokenMissingError as p,UserAlreadyExistsError as m,accessTokenPayloadSchema as h,createDefaultJwtConfig as g}from"@longzai-intelligence-auth/core";import _ from"node:crypto";import{JwtTokenVerifier as v}from"@longzai-intelligence-auth/jwt";import{Elysia as y}from"elysia";function b(t){return s(t)?400:t instanceof ee||t instanceof f||t instanceof p||t instanceof u||t instanceof l||t instanceof c||t instanceof e?401:o(t)?403:a(t)?404:t instanceof n||t instanceof m?409:t instanceof d?429:i(t)?409:r(t)||t.code===`PASSWORD_POLICY_VIOLATION`?422:500}function x(e,t){let n=Buffer.from(e,`utf-8`),r=Buffer.from(t,`utf-8`);if(n.length!==r.length){let e=Buffer.alloc(n.length);return _.timingSafeEqual(n,e),!1}return _.timingSafeEqual(n,r)}function S(e){let{token:t,getToken:n,subject:r=`static`,tenantId:i}=e;if(!t&&!n)throw Error(`createStaticTokenVerifier 需要至少提供 token 或 getToken 之一`);let a=()=>{if(n){let e=n();if(e)return e}return t??null};return{async verifyAccessToken(e){let t=a();return t?x(e,t)?{success:!0,payload:{sub:r,type:`access`,...i!==void 0&&{tenantId:i}}}:{success:!1,error:`静态令牌不匹配`}:{success:!1,error:`静态令牌未配置`}},async verifyRefreshToken(){return{success:!1,error:`静态令牌模式不支持 refresh token`}}}}function C(e){let{cookieName:t}=e;return{extract(e){let n=e.headers.get(`cookie`);if(!n)return null;let r=n.split(`;`);for(let e of r){let n=e.indexOf(`=`);if(n!==-1&&e.slice(0,n).trim()===t)return e.slice(n+1).trim()||null}return null}}}function w(e={}){let{headerName:t=`authorization`}=e;return{extract(e){let n=e.headers.get(t);if(!n)return null;let r=n.split(` `);return r.length!==2||r[0]!==`Bearer`||!r[1]?null:r[1]}}}function T(e={}){let{headerName:t=`x-api-key`}=e;return{extract(e){return e.headers.get(t)??e.headers.get(t.toLowerCase())??null}}}function E(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function D(e,t,n){if(e)return e;if(!t)throw Error(`${n} 需要至少提供 jwt 或 verifier 之一`);return new v(E(t))}async function O(e,t,n,r){let i=t.extract(e);return i?n.verifyAccessToken(i):{success:!1,error:r}}function k(e){let{cookieName:t,jwt:n,verifier:r,extractor:i}=e,a=D(r,n,`createCookieJwtVerifier`),o=i??C({cookieName:t});return{async verify(e){return O(e,o,a,`请求未携带 cookie 凭证`)}}}function A(e){let{jwt:t,verifier:n,extractor:r}=e,i=D(n,t,`createBearerJwtVerifier`),a=r??w();return{async verify(e){return O(e,a,i,`请求未携带 Bearer 凭证`)}}}function te(e,t){return{userId:e.sub,tenantId:e.tenantId??t,roles:e.roles??[],permissions:e.permissions??[],authMethod:`jwt`}}function j(e){return{userId:``,tenantId:e,roles:[],permissions:[],authMethod:`jwt`}}function M(e,t){return t.some(t=>t.endsWith(`/*`)?e.startsWith(t.slice(0,-2)):e===t)}function N(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function P(e,t){return e||new v(N(t??g()))}function F(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,publicPaths:i=[],extendSchema:a}=e,o=P(t,n);return new y({name:`@longzai-intelligence-auth/jwt-verify`}).derive(async({request:e})=>{if(M(new URL(e.url).pathname,i)){let e=a?a.parse({}):{};return{auth:{...j(r),...e}}}let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(!n)throw new p;let s=await o.verifyAccessToken(n);if(!s.success||!s.payload)throw new f(s.error);h.parse(s.payload);let c=te(s.payload,r),l=a?a.parse(s.payload):{};return{auth:{...c,...l}}}).as(`scoped`)}function I(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function L(e,t){return e||new v(I(t??g()))}function R(e){let{verifier:t,jwt:n,defaultTenantId:r=`default`,extendSchema:i}=e,a=L(t,n);return new y({name:`@longzai-intelligence-auth/optional-jwt`}).derive(async({request:e})=>{let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(!n)return{auth:null};try{let e=await a.verifyAccessToken(n);if(!e.success||!e.payload)return{auth:null};h.parse(e.payload);let t=e.payload,o={userId:t.sub,tenantId:t.tenantId??r,roles:t.roles??[],permissions:t.permissions??[],authMethod:`jwt`},s=i?i.parse(t):{};return{auth:{...o,...s}}}catch{return{auth:null}}}).as(`scoped`)}function z(e,t,n){return e.some(e=>e.resource===`*`&&e.action===`*`||e.resource===t&&e.action===`*`||e.resource===`*`&&e.action===n||e.resource===t&&e.action===n)}function B(e,t,n){let r=`${t}:${n}`;return e.includes(r)||e.includes(`${t}:*`)||e.includes(`*:${n}`)||e.includes(`*:*`)}function V(e={}){let{userRole:t}=e;return new y({name:`@longzai-intelligence-auth/rbac`}).derive({as:`scoped`},async e=>{let n=e.auth;if(!n)throw new p;let r=n.userId,i=n.tenantId;return{requirePermission:async(e,a)=>{if(t){if(z(await t.findPermissionsByUserId(r,i),e,a))return;let{PermissionDeniedError:n}=await import(`@longzai-intelligence/error`);throw new n(e,a)}if(B(n.permissions??[],e,a))return;let{PermissionDeniedError:o}=await import(`@longzai-intelligence/error`);throw new o(e,a)}}}).as(`global`)}function H(e){let{header:t=`x-api-key`,validator:n}=e;return new y({name:`@longzai-intelligence-auth/api-key`}).derive(async({request:e})=>{let r=e.headers.get(t)??e.headers.get(t.toLowerCase());if(!r)throw new p(`缺少 API Key`);let i=await n(r);if(!i)throw new f(`无效的 API Key`);return{auth:{userId:i.principalId,tenantId:i.tenantId??`default`,roles:i.roles,permissions:i.permissions,authMethod:`api-key`}}}).as(`scoped`)}function U(e){return async t=>{let n=e[t];return n?{principalId:n.principalId,tenantId:n.tenantId,roles:n.roles,permissions:n.permissions}:null}}function W(e){return{authMethod:`jwt`,userId:e.userId,tenantId:e.tenantId,roles:e.roles,permissions:e.permissions}}function G(e,t){return{authMethod:`api-key`,userId:e.principalId,tenantId:e.tenantId??t,roles:e.roles,permissions:e.permissions}}function K(e){return{algorithm:`HS256`,secret:e.secret,accessExpiresIn:e.accessExpiresIn,refreshExpiresIn:e.refreshExpiresIn}}function q(e,t,n){let r=[];return e&&r.push(e),t&&t.length>0&&r.push(...t),r.length>0?r:[new v(K(n??g()))]}function J(e,t){return t.some(t=>t.endsWith(`/*`)?e.startsWith(t.slice(0,-2)):e===t)}async function Y(e,t){for(let n of e)try{let e=await n.verifyAccessToken(t);if(e.success&&e.payload)return{success:!0,payload:e.payload}}catch{}return null}function X(e){let{verifier:t,verifiers:n,jwt:r,defaultTenantId:i=`default`,apiKeyValidator:a,apiKeyHeader:o=`x-api-key`,publicPaths:s=[]}=e,c=q(t,n,r);return new y({name:`@longzai-intelligence-auth/composite-auth`}).derive(async({request:e})=>{if(J(new URL(e.url).pathname,s))return{auth:null};let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(n){let e=await Y(c,n);if(e)return h.parse(e.payload),{auth:W({userId:e.payload.sub,tenantId:e.payload.tenantId??i,roles:e.payload.roles??[],permissions:e.payload.permissions??[]})}}if(a){let t=e.headers.get(o)??e.headers.get(o.toLowerCase());if(t){let e=await a(t);if(e)return{auth:G(e,i)}}}return{auth:null}}).as(`scoped`)}function Z(e){let{verifier:t,verifiers:n,jwt:r,defaultTenantId:i=`default`,apiKeyValidator:a,apiKeyHeader:o=`x-api-key`,publicPaths:s=[]}=e,c=q(t,n,r);return new y({name:`@longzai-intelligence-auth/required-composite-auth`}).derive(async({request:e})=>{if(J(new URL(e.url).pathname,s))return{auth:{authMethod:`jwt`,userId:``,tenantId:i,roles:[],permissions:[]}};let t=e.headers.get(`Authorization`),n=t?.startsWith(`Bearer `)?t.slice(7):null;if(n){let e=await Y(c,n);if(e)return h.parse(e.payload),{auth:W({userId:e.payload.sub,tenantId:e.payload.tenantId??i,roles:e.payload.roles??[],permissions:e.payload.permissions??[]})}}if(a){let t=e.headers.get(o)??e.headers.get(o.toLowerCase());if(t){let e=await a(t);if(e)return{auth:G(e,i)}}}throw Error(`认证失败:需要提供有效的 JWT Token 或 API Key`)}).as(`scoped`)}function ne(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??`unknown`}function Q(e){let{limiter:t,logger:n}=e,r=e.keyGenerator??ne;return new y({name:`@longzai-intelligence-auth/rate-limit`}).derive(({request:e})=>({clientIp:r(e)})).derive(async({clientIp:e,request:r})=>{let i=new URL(r.url).pathname,a=`${e}:${i}`,o=await t.checkLimit(a);if(!o.allowed)throw n?.warn(`限流触发`,{clientIp:e,path:i}),new d;return{rateLimitRemaining:o.remaining}}).as(`global`)}function $(e={}){let{logger:n}=e;return new y({name:`@longzai-intelligence-auth/error-handler`}).onError(({code:e,error:r,set:i})=>{if(r instanceof t)return i.status=b(r),{code:r.code,message:r.message,...r.context&&Object.keys(r.context).length>0?{details:r.context}:{}};switch(e){case`VALIDATION`:return i.status=400,{code:`VALIDATION_ERROR`,message:`请求参数验证失败`,details:r.all.map(e=>({path:e.path,message:e.summary}))};case`NOT_FOUND`:return i.status=404,{code:`NOT_FOUND`,message:`未找到请求的资源`};case`PARSE`:return i.status=400,{code:`PARSE_ERROR`,message:`请求数据解析失败`};default:return n?.error(`未预期的错误`,{error:r instanceof Error?r.message:String(r),stack:r instanceof Error?r.stack:void 0}),i.status=500,{code:`INTERNAL_ERROR`,message:`服务器内部错误`}}}).as(`global`)}function re(e){let{logger:t}=e;return new y({name:`@longzai-intelligence-auth/logger`}).onRequest(({request:e})=>{t.info(`${e.method} ${e.url}`)}).onAfterResponse(({request:e,set:n})=>{t.info(`${e.method} ${e.url} ${n.status}`)}).onError(({request:e,error:n})=>{t.error(`${e.method} ${e.url} 请求错误`,{error:n instanceof Error?n.message:String(n)})}).as(`global`)}function ie(e){return new y({name:`@longzai-intelligence-auth/basic-preset`}).use(F({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,extendSchema:e.extendSchema})).use($({logger:e.logger}))}function ae(e){let t=e.apiKeyValidator?Z({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,apiKeyValidator:e.apiKeyValidator}):F({verifier:e.tokenVerifier,defaultTenantId:e.defaultTenantId,publicPaths:e.publicPaths,extendSchema:e.extendSchema}),n=new y({name:`@longzai-intelligence-auth/standard-preset`}).use(t).use(V({userRole:e.userRole}));return e.rateLimiter&&n.use(Q({limiter:e.rateLimiter,logger:e.logger})),n.use($({logger:e.logger}))}function oe(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??void 0}function se(e){return e.headers.get(`user-agent`)??void 0}export{T as createApiKeyExtractor,H as createApiKeyPlugin,ie as createBasicPreset,w as createBearerExtractor,A as createBearerJwtVerifier,X as createCompositeAuthPlugin,C as createCookieExtractor,k as createCookieJwtVerifier,$ as createErrorHandlerPlugin,U as createInMemoryApiKeyValidator,F as createJwtVerifyPlugin,re as createLoggerPlugin,R as createOptionalJwtPlugin,Q as createRateLimitPlugin,V as createRbacPlugin,Z as createRequiredCompositeAuthPlugin,ae as createStandardPreset,S as createStaticTokenVerifier,oe as extractClientIp,se as extractUserAgent,b as mapDomainErrorToStatus};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longzai-intelligence-auth/elysia",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"clean": "rimraf dist out .cache"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@longzai-intelligence-auth/core": "0.0.
|
|
40
|
-
"@longzai-intelligence-auth/jwt": "0.0.
|
|
39
|
+
"@longzai-intelligence-auth/core": "0.0.8",
|
|
40
|
+
"@longzai-intelligence-auth/jwt": "0.0.8",
|
|
41
41
|
"@longzai-intelligence/error": "^0.0.5",
|
|
42
42
|
"@elysiajs/bearer": "^1.3",
|
|
43
43
|
"zod": "^3.24"
|