@longzai-intelligence-auth/elysia 0.0.2

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.
Files changed (35) hide show
  1. package/dist/core/auth-strategy.d.ts +14 -0
  2. package/dist/core/auth-strategy.js +98 -0
  3. package/dist/core/error-mapper.d.ts +2 -0
  4. package/dist/core/error-mapper.js +27 -0
  5. package/dist/index.d.ts +25 -0
  6. package/dist/index.js +21 -0
  7. package/dist/plugin.d.ts +6 -0
  8. package/dist/plugin.js +39 -0
  9. package/dist/plugins/audit.plugin.d.ts +38 -0
  10. package/dist/plugins/audit.plugin.js +13 -0
  11. package/dist/plugins/error-handler.plugin.d.ts +46 -0
  12. package/dist/plugins/error-handler.plugin.js +45 -0
  13. package/dist/plugins/jwt-verify.plugin.d.ts +9 -0
  14. package/dist/plugins/jwt-verify.plugin.js +31 -0
  15. package/dist/plugins/logger.plugin.d.ts +33 -0
  16. package/dist/plugins/logger.plugin.js +16 -0
  17. package/dist/plugins/rate-limit.plugin.d.ts +40 -0
  18. package/dist/plugins/rate-limit.plugin.js +39 -0
  19. package/dist/plugins/rbac.plugin.d.ts +36 -0
  20. package/dist/plugins/rbac.plugin.js +42 -0
  21. package/dist/plugins/tenant.plugin.d.ts +38 -0
  22. package/dist/plugins/tenant.plugin.js +19 -0
  23. package/dist/presets/basic-preset.d.ts +11 -0
  24. package/dist/presets/basic-preset.js +11 -0
  25. package/dist/presets/standard-preset.d.ts +13 -0
  26. package/dist/presets/standard-preset.js +26 -0
  27. package/dist/routes/auth.routes.d.ts +17 -0
  28. package/dist/routes/auth.routes.js +145 -0
  29. package/dist/types/auth-plugin-config.d.ts +40 -0
  30. package/dist/types/auth-plugin-config.js +1 -0
  31. package/dist/utils/extract-client-ip.d.ts +1 -0
  32. package/dist/utils/extract-client-ip.js +5 -0
  33. package/dist/utils/extract-user-agent.d.ts +1 -0
  34. package/dist/utils/extract-user-agent.js +3 -0
  35. package/package.json +67 -0
@@ -0,0 +1,14 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ import type { JwtStrategyConfig, StandaloneStrategyConfig, EnvBasicStrategyConfig, StrategyContext, AuthPluginConfig } from "../types/auth-plugin-config";
4
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
5
+ export type AuthStrategy = {
6
+ context: StrategyContext;
7
+ createJwtVerifyPlugin(): AnyElysia;
8
+ supportsAuthRoutes: boolean;
9
+ };
10
+ export declare function createAuthStrategy(config: AuthPluginConfig): Promise<AuthStrategy>;
11
+ export declare function extractJwtConfig(config: JwtStrategyConfig | StandaloneStrategyConfig | EnvBasicStrategyConfig): JwtSignerConfig;
12
+ export declare function extractDefaultTenantId(config: JwtStrategyConfig | StandaloneStrategyConfig | EnvBasicStrategyConfig): string;
13
+ export declare function extractRegisterRoutes(config: AuthPluginConfig): boolean;
14
+ export {};
@@ -0,0 +1,98 @@
1
+ import { createJwtVerifyPlugin } from "../plugins/jwt-verify.plugin";
2
+ function createJwtStrategy(config) {
3
+ const defaultTenantId = config.defaultTenantId ?? "default";
4
+ return {
5
+ context: {
6
+ strategy: "jwt",
7
+ defaultTenantId,
8
+ registerRoutes: config.registerRoutes ?? false,
9
+ },
10
+ createJwtVerifyPlugin: () => createJwtVerifyPlugin({ jwt: config.jwt, defaultTenantId }),
11
+ supportsAuthRoutes: true,
12
+ };
13
+ }
14
+ function createStandaloneStrategy(config) {
15
+ console.warn("[@longzai-intelligence-auth/elysia] standalone 策略仅用于开发和测试环境,生产环境请使用 jwt 策略");
16
+ const defaultTenantId = config.defaultTenantId ?? "default";
17
+ return {
18
+ context: {
19
+ strategy: "standalone",
20
+ defaultTenantId,
21
+ registerRoutes: config.registerRoutes ?? true,
22
+ adapter: config.adapter,
23
+ passwordHasher: config.passwordHasher,
24
+ },
25
+ createJwtVerifyPlugin: () => createJwtVerifyPlugin({ jwt: config.jwt, defaultTenantId }),
26
+ supportsAuthRoutes: true,
27
+ };
28
+ }
29
+ async function createEnvBasicStrategy(config) {
30
+ const adminUsername = config.adminUsername ?? process.env.AUTH_ADMIN_USERNAME ?? "admin";
31
+ const adminPassword = config.adminPassword ?? process.env.AUTH_ADMIN_PASSWORD;
32
+ if (!adminPassword) {
33
+ throw new Error("[@longzai-intelligence-auth/elysia] env-basic 策略需要设置 AUTH_ADMIN_PASSWORD 环境变量或 adminPassword 配置项");
34
+ }
35
+ console.warn("[@longzai-intelligence-auth/elysia] env-basic 策略仅用于开发和测试环境,生产环境请使用 jwt 或 standalone 策略");
36
+ let createInMemoryAuthBackend;
37
+ let createEnvBasicPasswordHasher;
38
+ try {
39
+ const adapter = await import("@longzai-intelligence-auth/identity-adapter");
40
+ createInMemoryAuthBackend = adapter.createInMemoryAuthBackend;
41
+ createEnvBasicPasswordHasher = adapter.createEnvBasicPasswordHasher;
42
+ }
43
+ catch {
44
+ throw new Error("[@longzai-intelligence-auth/elysia] env-basic 策略需要安装 @longzai-intelligence-auth/identity-adapter 包");
45
+ }
46
+ const defaultTenantId = config.defaultTenantId ?? "default";
47
+ const backend = createInMemoryAuthBackend({
48
+ adminUsername,
49
+ adminPassword,
50
+ jwtConfig: config.jwt,
51
+ });
52
+ const passwordHasher = createEnvBasicPasswordHasher();
53
+ return {
54
+ context: {
55
+ strategy: "env-basic",
56
+ defaultTenantId,
57
+ registerRoutes: config.registerRoutes ?? true,
58
+ adapter: backend,
59
+ passwordHasher,
60
+ },
61
+ createJwtVerifyPlugin: () => createJwtVerifyPlugin({ jwt: config.jwt, defaultTenantId }),
62
+ supportsAuthRoutes: true,
63
+ };
64
+ }
65
+ export async function createAuthStrategy(config) {
66
+ switch (config.strategy) {
67
+ case "jwt":
68
+ return createJwtStrategy(config);
69
+ case "standalone":
70
+ return createStandaloneStrategy(config);
71
+ case "env-basic":
72
+ return createEnvBasicStrategy(config);
73
+ default: {
74
+ const _exhaustive = config;
75
+ throw new Error(`未知的认证策略: ${JSON.stringify(_exhaustive)}`);
76
+ }
77
+ }
78
+ }
79
+ export function extractJwtConfig(config) {
80
+ return config.jwt;
81
+ }
82
+ export function extractDefaultTenantId(config) {
83
+ return config.defaultTenantId ?? "default";
84
+ }
85
+ export function extractRegisterRoutes(config) {
86
+ switch (config.strategy) {
87
+ case "jwt":
88
+ return config.registerRoutes ?? false;
89
+ case "standalone":
90
+ return config.registerRoutes ?? true;
91
+ case "env-basic":
92
+ return config.registerRoutes ?? true;
93
+ default: {
94
+ const _exhaustive = config;
95
+ return false;
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,2 @@
1
+ import type { DomainError } from "@longzai-intelligence/error";
2
+ export declare function mapDomainErrorToStatus(error: DomainError): number;
@@ -0,0 +1,27 @@
1
+ import { AuthenticationError, DuplicateEntityError, isValidationError, isEntityNotFoundError, isPermissionDeniedError, isBusinessRuleError, isConcurrencyError, } from "@longzai-intelligence/error";
2
+ import { TokenExpiredError, TokenInvalidError, TokenMissingError, MfaRequiredError, InvalidCredentialsError, AccountDisabledError, RateLimitExceededError, } from "@longzai-intelligence-auth/core";
3
+ export function mapDomainErrorToStatus(error) {
4
+ if (isValidationError(error))
5
+ return 400;
6
+ if (error instanceof TokenExpiredError ||
7
+ error instanceof TokenInvalidError ||
8
+ error instanceof TokenMissingError ||
9
+ error instanceof MfaRequiredError ||
10
+ error instanceof InvalidCredentialsError ||
11
+ error instanceof AccountDisabledError ||
12
+ error instanceof AuthenticationError)
13
+ return 401;
14
+ if (isPermissionDeniedError(error))
15
+ return 403;
16
+ if (isEntityNotFoundError(error))
17
+ return 404;
18
+ if (error instanceof DuplicateEntityError)
19
+ return 409;
20
+ if (error instanceof RateLimitExceededError)
21
+ return 429;
22
+ if (isConcurrencyError(error))
23
+ return 409;
24
+ if (isBusinessRuleError(error))
25
+ return 422;
26
+ return 500;
27
+ }
@@ -0,0 +1,25 @@
1
+ export { createAuthPlugin } from "./plugin";
2
+ export type { AuthPluginConfig, AuthPluginOptions, JwtStrategyConfig, StandaloneStrategyConfig, EnvBasicStrategyConfig, StrategyName, StrategyContext, } from "./types/auth-plugin-config";
3
+ export { createAuthStrategy, extractJwtConfig, extractDefaultTenantId, extractRegisterRoutes } from "./core/auth-strategy";
4
+ export type { AuthStrategy } from "./core/auth-strategy";
5
+ export { mapDomainErrorToStatus } from "./core/error-mapper";
6
+ export { createJwtVerifyPlugin } from "./plugins/jwt-verify.plugin";
7
+ export type { JwtVerifyPluginOptions } from "./plugins/jwt-verify.plugin";
8
+ export { createRbacPlugin } from "./plugins/rbac.plugin";
9
+ export type { RbacPluginOptions } from "./plugins/rbac.plugin";
10
+ export { createTenantPlugin } from "./plugins/tenant.plugin";
11
+ export type { TenantPluginOptions } from "./plugins/tenant.plugin";
12
+ export { createRateLimitPlugin } from "./plugins/rate-limit.plugin";
13
+ export type { RateLimitPluginOptions } from "./plugins/rate-limit.plugin";
14
+ export { createAuditPlugin } from "./plugins/audit.plugin";
15
+ export type { AuditPluginOptions } from "./plugins/audit.plugin";
16
+ export { createErrorHandlerPlugin } from "./plugins/error-handler.plugin";
17
+ export type { ErrorHandlerPluginOptions } from "./plugins/error-handler.plugin";
18
+ export { createLoggerPlugin } from "./plugins/logger.plugin";
19
+ export type { LoggerPluginOptions } from "./plugins/logger.plugin";
20
+ export { createBasicPreset } from "./presets/basic-preset";
21
+ export type { BasicPresetOptions } from "./presets/basic-preset";
22
+ export { createStandardPreset } from "./presets/standard-preset";
23
+ export type { StandardPresetOptions } from "./presets/standard-preset";
24
+ export { extractClientIp } from "./utils/extract-client-ip";
25
+ export { extractUserAgent } from "./utils/extract-user-agent";
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // @bun
2
+ export {
3
+ mapDomainErrorToStatus,
4
+ extractUserAgent,
5
+ extractRegisterRoutes,
6
+ extractJwtConfig,
7
+ extractDefaultTenantId,
8
+ extractClientIp,
9
+ createTenantPlugin,
10
+ createStandardPreset,
11
+ createRbacPlugin,
12
+ createRateLimitPlugin,
13
+ createLoggerPlugin,
14
+ createJwtVerifyPlugin,
15
+ createErrorHandlerPlugin,
16
+ createBasicPreset,
17
+ createAuthStrategy,
18
+ createAuthPlugin,
19
+ createAuditPlugin,
20
+ createAuditElysiaPlugin
21
+ };
@@ -0,0 +1,6 @@
1
+ import { Elysia } from "elysia";
2
+ import type { AuthPluginOptions } from "./types/auth-plugin-config";
3
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
4
+ export declare function createAuthPlugin(options: AuthPluginOptions): Promise<AnyElysia>;
5
+ export type { AuthPluginConfig, AuthPluginOptions, StrategyContext, StrategyName } from "./types/auth-plugin-config";
6
+ export type { AuthStrategy } from "./core/auth-strategy";
package/dist/plugin.js ADDED
@@ -0,0 +1,39 @@
1
+ import { Elysia } from "elysia";
2
+ import { createAuthStrategy, extractJwtConfig, extractDefaultTenantId, extractRegisterRoutes } from "./core/auth-strategy";
3
+ import { createAuthRoutes, createMeRoute } from "./routes/auth.routes";
4
+ import { createErrorHandlerPlugin } from "./plugins/error-handler.plugin";
5
+ export async function createAuthPlugin(options) {
6
+ const { logger, ...config } = options;
7
+ const strategy = await createAuthStrategy(config);
8
+ const jwtConfig = extractJwtConfig(config);
9
+ const defaultTenantId = extractDefaultTenantId(config);
10
+ const registerRoutes = extractRegisterRoutes(config);
11
+ let plugin = new Elysia({ name: "@longzai-intelligence-auth/plugin" })
12
+ .use(strategy.createJwtVerifyPlugin())
13
+ .use(createErrorHandlerPlugin({ logger }));
14
+ if (registerRoutes && config.strategy === "standalone") {
15
+ const standaloneConfig = config;
16
+ plugin = plugin.use(createAuthRoutes({
17
+ jwt: jwtConfig,
18
+ adapter: standaloneConfig.adapter,
19
+ passwordHasher: standaloneConfig.passwordHasher,
20
+ defaultTenantId,
21
+ }));
22
+ }
23
+ if (registerRoutes && config.strategy === "env-basic") {
24
+ const envBasicConfig = config;
25
+ plugin = plugin.use(createAuthRoutes({
26
+ jwt: jwtConfig,
27
+ adapter: strategy.context.adapter,
28
+ passwordHasher: strategy.context.passwordHasher,
29
+ defaultTenantId,
30
+ }));
31
+ }
32
+ if (registerRoutes && config.strategy === "jwt") {
33
+ plugin = plugin.use(createMeRoute({
34
+ jwt: jwtConfig,
35
+ defaultTenantId,
36
+ }));
37
+ }
38
+ return plugin;
39
+ }
@@ -0,0 +1,38 @@
1
+ import { Elysia } from "elysia";
2
+ import type { AuthBackendPort } from "@longzai-intelligence-auth/core";
3
+ import type { AuditLogEntry } from "@longzai-intelligence-auth/core";
4
+ export type AuditPluginOptions = {
5
+ adapter: AuthBackendPort;
6
+ };
7
+ export declare function createAuditPlugin(options: AuditPluginOptions): Elysia<"", {
8
+ decorator: {};
9
+ store: {};
10
+ derive: {};
11
+ resolve: {};
12
+ }, {
13
+ typebox: {};
14
+ error: {};
15
+ }, {
16
+ schema: {};
17
+ standaloneSchema: {};
18
+ macro: {};
19
+ macroFn: {};
20
+ parser: {};
21
+ response: {};
22
+ }, {}, {
23
+ derive: {
24
+ readonly recordAudit: (entry: Omit<AuditLogEntry, never>) => void;
25
+ };
26
+ resolve: {};
27
+ schema: {};
28
+ standaloneSchema: {};
29
+ response: import("elysia").ExtractErrorFromHandle<{
30
+ readonly recordAudit: (entry: Omit<AuditLogEntry, never>) => void;
31
+ }>;
32
+ }, {
33
+ derive: {};
34
+ resolve: {};
35
+ schema: {};
36
+ standaloneSchema: {};
37
+ response: {};
38
+ }>;
@@ -0,0 +1,13 @@
1
+ import { Elysia } from "elysia";
2
+ export function createAuditPlugin(options) {
3
+ const { adapter } = options;
4
+ return new Elysia({ name: "@longzai-intelligence-auth/audit" })
5
+ .derive(() => ({
6
+ recordAudit: (entry) => {
7
+ adapter.audit.write(entry).catch((error) => {
8
+ console.error("审计日志写入失败:", error);
9
+ });
10
+ },
11
+ }))
12
+ .as("scoped");
13
+ }
@@ -0,0 +1,46 @@
1
+ import { Elysia } from "elysia";
2
+ import type { LoggerService } from "@longzai-intelligence-auth/core";
3
+ export type ErrorHandlerPluginOptions = {
4
+ logger?: LoggerService;
5
+ };
6
+ export declare function createErrorHandlerPlugin(options?: ErrorHandlerPluginOptions): Elysia<"", {
7
+ decorator: {};
8
+ store: {};
9
+ derive: {};
10
+ resolve: {};
11
+ }, {
12
+ typebox: {};
13
+ error: {};
14
+ }, {
15
+ schema: {};
16
+ standaloneSchema: {};
17
+ macro: {};
18
+ macroFn: {};
19
+ parser: {};
20
+ response: {};
21
+ }, {}, {
22
+ derive: {};
23
+ resolve: {};
24
+ schema: {};
25
+ standaloneSchema: {};
26
+ response: {};
27
+ }, {
28
+ derive: {};
29
+ resolve: {};
30
+ schema: {};
31
+ standaloneSchema: {};
32
+ response: {
33
+ 200: {
34
+ code: string;
35
+ message: string;
36
+ details?: Record<string, unknown> | undefined;
37
+ } | {
38
+ code: string;
39
+ message: string;
40
+ details: {
41
+ path: string;
42
+ message: string | undefined;
43
+ }[];
44
+ };
45
+ };
46
+ }>;
@@ -0,0 +1,45 @@
1
+ import { Elysia } from "elysia";
2
+ import { DomainError } from "@longzai-intelligence/error";
3
+ import { mapDomainErrorToStatus } from "../core/error-mapper";
4
+ export function createErrorHandlerPlugin(options = {}) {
5
+ const { logger } = options;
6
+ return new Elysia({ name: "@longzai-intelligence-auth/error-handler" })
7
+ .onError(({ code, error, set }) => {
8
+ if (error instanceof DomainError) {
9
+ const status = mapDomainErrorToStatus(error);
10
+ set.status = status;
11
+ return {
12
+ code: error.code,
13
+ message: error.message,
14
+ ...(error.context && Object.keys(error.context).length > 0
15
+ ? { details: error.context }
16
+ : {}),
17
+ };
18
+ }
19
+ switch (code) {
20
+ case "VALIDATION":
21
+ set.status = 400;
22
+ return {
23
+ code: "VALIDATION_ERROR",
24
+ message: "请求参数验证失败",
25
+ details: error.all.map((e) => ({
26
+ path: e.path,
27
+ message: e.summary,
28
+ })),
29
+ };
30
+ case "NOT_FOUND":
31
+ set.status = 404;
32
+ return { code: "NOT_FOUND", message: "未找到请求的资源" };
33
+ case "PARSE":
34
+ set.status = 400;
35
+ return { code: "PARSE_ERROR", message: "请求数据解析失败" };
36
+ default:
37
+ logger?.error("未预期的错误", {
38
+ error: error instanceof Error ? error.message : String(error),
39
+ stack: error instanceof Error ? error.stack : undefined,
40
+ });
41
+ set.status = 500;
42
+ return { code: "INTERNAL_ERROR", message: "服务器内部错误" };
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,9 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ export type JwtVerifyPluginOptions = {
4
+ jwt: JwtSignerConfig;
5
+ defaultTenantId?: string;
6
+ };
7
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
8
+ export declare function createJwtVerifyPlugin(options: JwtVerifyPluginOptions): AnyElysia;
9
+ export {};
@@ -0,0 +1,31 @@
1
+ import { Elysia } from "elysia";
2
+ import { createJwtVerifier } from "@longzai-intelligence-auth/jwt";
3
+ import { TokenMissingError, TokenInvalidError } from "@longzai-intelligence-auth/core";
4
+ export function createJwtVerifyPlugin(options) {
5
+ const { jwt: jwtConfig, defaultTenantId = "default" } = options;
6
+ let verifierPromise = null;
7
+ const getVerifier = () => {
8
+ if (!verifierPromise) {
9
+ verifierPromise = createJwtVerifier(jwtConfig);
10
+ }
11
+ return verifierPromise;
12
+ };
13
+ return new Elysia({ name: "@longzai-intelligence-auth/jwt-verify" })
14
+ .derive(async ({ request }) => {
15
+ const authHeader = request.headers.get("Authorization");
16
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
17
+ if (!bearer) {
18
+ throw new TokenMissingError();
19
+ }
20
+ const verifier = await getVerifier();
21
+ const result = await verifier.verifyAccessToken(bearer);
22
+ if (!result.success || !result.payload) {
23
+ throw new TokenInvalidError(result.error);
24
+ }
25
+ const payload = result.payload;
26
+ const userId = payload.sub;
27
+ const tenantId = payload.tenantId ?? defaultTenantId;
28
+ return { userId, tenantId };
29
+ })
30
+ .as("scoped");
31
+ }
@@ -0,0 +1,33 @@
1
+ import { Elysia } from "elysia";
2
+ import type { LoggerService } from "@longzai-intelligence-auth/core";
3
+ export type LoggerPluginOptions = {
4
+ logger: LoggerService;
5
+ };
6
+ export declare function createLoggerPlugin(options: LoggerPluginOptions): Elysia<"", {
7
+ decorator: {};
8
+ store: {};
9
+ derive: {};
10
+ resolve: {};
11
+ }, {
12
+ typebox: {};
13
+ error: {};
14
+ }, {
15
+ schema: {};
16
+ standaloneSchema: {};
17
+ macro: {};
18
+ macroFn: {};
19
+ parser: {};
20
+ response: {};
21
+ }, {}, {
22
+ derive: {};
23
+ resolve: {};
24
+ schema: {};
25
+ standaloneSchema: {};
26
+ response: {};
27
+ }, {
28
+ derive: {};
29
+ resolve: {};
30
+ schema: {};
31
+ standaloneSchema: {};
32
+ response: {};
33
+ }>;
@@ -0,0 +1,16 @@
1
+ import { Elysia } from "elysia";
2
+ export function createLoggerPlugin(options) {
3
+ const { logger } = options;
4
+ return new Elysia({ name: "@longzai-intelligence-auth/logger" })
5
+ .onRequest(({ request }) => {
6
+ logger.info(`${request.method} ${request.url}`);
7
+ })
8
+ .onAfterResponse(({ request, set }) => {
9
+ logger.info(`${request.method} ${request.url} ${set.status}`);
10
+ })
11
+ .onError(({ request, error }) => {
12
+ logger.error(`${request.method} ${request.url} 请求错误`, {
13
+ error: error instanceof Error ? error.message : String(error),
14
+ });
15
+ });
16
+ }
@@ -0,0 +1,40 @@
1
+ import { Elysia } from "elysia";
2
+ import type { RateLimitConfig, LoggerService } from "@longzai-intelligence-auth/core";
3
+ export type RateLimitPluginOptions = RateLimitConfig & {
4
+ logger?: LoggerService;
5
+ keyGenerator?: (request: Request) => string;
6
+ };
7
+ export declare function createRateLimitPlugin(options?: RateLimitPluginOptions): Elysia<"", {
8
+ decorator: {};
9
+ store: {};
10
+ derive: {};
11
+ resolve: {};
12
+ }, {
13
+ typebox: {};
14
+ error: {};
15
+ }, {
16
+ schema: {};
17
+ standaloneSchema: {};
18
+ macro: {};
19
+ macroFn: {};
20
+ parser: {};
21
+ response: {};
22
+ }, {}, {
23
+ derive: {};
24
+ resolve: {};
25
+ schema: {};
26
+ standaloneSchema: {};
27
+ response: {};
28
+ }, {
29
+ derive: {
30
+ readonly clientIp: string;
31
+ } & {
32
+ readonly rateLimitRemaining: number;
33
+ };
34
+ resolve: {};
35
+ schema: {};
36
+ standaloneSchema: {};
37
+ response: import("elysia").ExtractErrorFromHandle<{
38
+ readonly rateLimitRemaining: number;
39
+ }>;
40
+ }>;
@@ -0,0 +1,39 @@
1
+ import { Elysia } from "elysia";
2
+ import { createMemoryRateLimiter } from "@longzai-intelligence-auth/rate-limit";
3
+ import { RateLimitExceededError } from "@longzai-intelligence-auth/core";
4
+ function defaultKeyGenerator(request) {
5
+ return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
6
+ ?? request.headers.get("x-real-ip")
7
+ ?? "unknown";
8
+ }
9
+ export function createRateLimitPlugin(options = { windowSeconds: 60, maxRequests: 100 }) {
10
+ const { logger } = options;
11
+ const keyGen = options.keyGenerator ?? defaultKeyGenerator;
12
+ const limiter = createMemoryRateLimiter({
13
+ windowSeconds: options.windowSeconds,
14
+ maxRequests: options.maxRequests,
15
+ });
16
+ const timer = setInterval(() => {
17
+ limiter.cleanup();
18
+ }, 60_000);
19
+ if (typeof process !== "undefined" && typeof process.on === "function") {
20
+ process.on("exit", () => clearInterval(timer));
21
+ }
22
+ return new Elysia({ name: "@longzai-intelligence-auth/rate-limit" })
23
+ .derive(({ request }) => {
24
+ const clientIp = keyGen(request);
25
+ return { clientIp };
26
+ })
27
+ .derive(async ({ clientIp, request }) => {
28
+ const url = new URL(request.url);
29
+ const path = url.pathname;
30
+ const key = `${clientIp}:${path}`;
31
+ const allowed = await limiter.check(key);
32
+ if (!allowed) {
33
+ logger?.warn("限流触发", { clientIp, path });
34
+ throw new RateLimitExceededError();
35
+ }
36
+ const remaining = options.maxRequests - limiter.getCount(key);
37
+ return { rateLimitRemaining: remaining };
38
+ });
39
+ }
@@ -0,0 +1,36 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ export type RbacPluginOptions = {
4
+ jwt: JwtSignerConfig;
5
+ defaultTenantId?: string;
6
+ };
7
+ export declare function createRbacPlugin(options: RbacPluginOptions): Elysia<"", {
8
+ decorator: {};
9
+ store: {};
10
+ derive: {};
11
+ resolve: {};
12
+ }, {
13
+ typebox: {};
14
+ error: {};
15
+ }, {
16
+ schema: {};
17
+ standaloneSchema: {};
18
+ macro: Partial<{}>;
19
+ macroFn: ({ onBeforeHandle }: any) => {
20
+ permission(permission: string): void;
21
+ };
22
+ parser: {};
23
+ response: {};
24
+ }, {}, {
25
+ derive: {};
26
+ resolve: {};
27
+ schema: {};
28
+ standaloneSchema: {};
29
+ response: {};
30
+ }, {
31
+ derive: {};
32
+ resolve: {};
33
+ schema: {};
34
+ standaloneSchema: {};
35
+ response: {};
36
+ }>;
@@ -0,0 +1,42 @@
1
+ import { Elysia } from "elysia";
2
+ import { createRoleChecker } from "@longzai-intelligence-auth/rbac";
3
+ import { createJwtVerifier } from "@longzai-intelligence-auth/jwt";
4
+ import { TokenMissingError, TokenInvalidError } from "@longzai-intelligence-auth/core";
5
+ export function createRbacPlugin(options) {
6
+ const { jwt: jwtConfig, defaultTenantId = "default" } = options;
7
+ const roleChecker = createRoleChecker();
8
+ let verifierPromise = null;
9
+ const getVerifier = () => {
10
+ if (!verifierPromise) {
11
+ verifierPromise = createJwtVerifier(jwtConfig);
12
+ }
13
+ return verifierPromise;
14
+ };
15
+ return new Elysia({ name: "@longzai-intelligence-auth/rbac" })
16
+ .macro(({ onBeforeHandle }) => ({
17
+ permission(permission) {
18
+ onBeforeHandle(async ({ request }) => {
19
+ const authHeader = request.headers.get("Authorization");
20
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
21
+ if (!bearer) {
22
+ throw new TokenMissingError();
23
+ }
24
+ const verifier = await getVerifier();
25
+ const result = await verifier.verifyAccessToken(bearer);
26
+ if (!result.success || !result.payload) {
27
+ throw new TokenInvalidError(result.error);
28
+ }
29
+ const payload = result.payload;
30
+ const [resource, action] = permission.split(":");
31
+ if (!resource || !action) {
32
+ throw new Error(`无效的权限格式: ${permission},应为 resource:action`);
33
+ }
34
+ const hasPermission = roleChecker.hasPermission(payload, resource, action);
35
+ if (!hasPermission) {
36
+ const { PermissionDeniedError } = await import("@longzai-intelligence/error");
37
+ throw new PermissionDeniedError(resource, action);
38
+ }
39
+ });
40
+ },
41
+ }));
42
+ }
@@ -0,0 +1,38 @@
1
+ import { Elysia } from "elysia";
2
+ import type { AuthBackendPort } from "@longzai-intelligence-auth/core";
3
+ export type TenantPluginOptions = {
4
+ defaultTenantId?: string;
5
+ adapter?: AuthBackendPort;
6
+ };
7
+ export declare function createTenantPlugin(options?: TenantPluginOptions): Elysia<"", {
8
+ decorator: {};
9
+ store: {};
10
+ derive: {};
11
+ resolve: {};
12
+ }, {
13
+ typebox: {};
14
+ error: {};
15
+ }, {
16
+ schema: {};
17
+ standaloneSchema: {};
18
+ macro: {};
19
+ macroFn: {};
20
+ parser: {};
21
+ response: {};
22
+ }, {}, {
23
+ derive: {
24
+ tenantId: string;
25
+ };
26
+ resolve: {};
27
+ schema: {};
28
+ standaloneSchema: {};
29
+ response: import("elysia").ExtractErrorFromHandle<{
30
+ tenantId: string;
31
+ }>;
32
+ }, {
33
+ derive: {};
34
+ resolve: {};
35
+ schema: {};
36
+ standaloneSchema: {};
37
+ response: {};
38
+ }>;
@@ -0,0 +1,19 @@
1
+ import { Elysia } from "elysia";
2
+ export function createTenantPlugin(options = {}) {
3
+ const { defaultTenantId = "default", adapter } = options;
4
+ return new Elysia({ name: "@longzai-intelligence-auth/tenant" })
5
+ .derive(({ request }) => {
6
+ const headerTenantId = request.headers.get("x-tenant-id");
7
+ return { tenantId: headerTenantId ?? defaultTenantId };
8
+ })
9
+ .as("scoped")
10
+ .onBeforeHandle(async ({ tenantId }) => {
11
+ if (adapter && tenantId !== defaultTenantId) {
12
+ const result = await adapter.tenant.validateStatus(tenantId);
13
+ if (!result.valid) {
14
+ const { PermissionDeniedError } = await import("@longzai-intelligence/error");
15
+ throw new PermissionDeniedError("tenant", "access");
16
+ }
17
+ }
18
+ });
19
+ }
@@ -0,0 +1,11 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ import type { LoggerService } from "@longzai-intelligence-auth/core";
4
+ export type BasicPresetOptions = {
5
+ jwt: JwtSignerConfig;
6
+ defaultTenantId?: string;
7
+ logger?: LoggerService;
8
+ };
9
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
10
+ export declare function createBasicPreset(options: BasicPresetOptions): AnyElysia;
11
+ export {};
@@ -0,0 +1,11 @@
1
+ import { Elysia } from "elysia";
2
+ import { createJwtVerifyPlugin } from "../plugins/jwt-verify.plugin";
3
+ import { createErrorHandlerPlugin } from "../plugins/error-handler.plugin";
4
+ export function createBasicPreset(options) {
5
+ return new Elysia({ name: "@longzai-intelligence-auth/basic-preset" })
6
+ .use(createJwtVerifyPlugin({
7
+ jwt: options.jwt,
8
+ defaultTenantId: options.defaultTenantId,
9
+ }))
10
+ .use(createErrorHandlerPlugin({ logger: options.logger }));
11
+ }
@@ -0,0 +1,13 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ import type { AuthBackendPort } from "@longzai-intelligence-auth/core";
4
+ import type { LoggerService } from "@longzai-intelligence-auth/core";
5
+ export type StandardPresetOptions = {
6
+ jwt: JwtSignerConfig;
7
+ defaultTenantId?: string;
8
+ adapter: AuthBackendPort;
9
+ logger?: LoggerService;
10
+ };
11
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
12
+ export declare function createStandardPreset(options: StandardPresetOptions): AnyElysia;
13
+ export {};
@@ -0,0 +1,26 @@
1
+ import { Elysia } from "elysia";
2
+ import { createJwtVerifyPlugin } from "../plugins/jwt-verify.plugin";
3
+ import { createRbacPlugin } from "../plugins/rbac.plugin";
4
+ import { createTenantPlugin } from "../plugins/tenant.plugin";
5
+ import { createRateLimitPlugin } from "../plugins/rate-limit.plugin";
6
+ import { createErrorHandlerPlugin } from "../plugins/error-handler.plugin";
7
+ export function createStandardPreset(options) {
8
+ return new Elysia({ name: "@longzai-intelligence-auth/standard-preset" })
9
+ .use(createJwtVerifyPlugin({
10
+ jwt: options.jwt,
11
+ defaultTenantId: options.defaultTenantId,
12
+ }))
13
+ .use(createRbacPlugin({
14
+ jwt: options.jwt,
15
+ defaultTenantId: options.defaultTenantId,
16
+ }))
17
+ .use(createTenantPlugin({
18
+ defaultTenantId: options.defaultTenantId,
19
+ adapter: options.adapter,
20
+ }))
21
+ .use(createRateLimitPlugin({
22
+ windowSeconds: 60,
23
+ maxRequests: 100,
24
+ }))
25
+ .use(createErrorHandlerPlugin({ logger: options.logger }));
26
+ }
@@ -0,0 +1,17 @@
1
+ import { Elysia } from "elysia";
2
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
3
+ import type { AuthBackendPort, PasswordHasher } from "@longzai-intelligence-auth/core";
4
+ export type AuthRoutesConfig = {
5
+ jwt: JwtSignerConfig;
6
+ adapter: AuthBackendPort;
7
+ passwordHasher: PasswordHasher;
8
+ defaultTenantId: string;
9
+ };
10
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
11
+ export declare function createAuthRoutes(config: AuthRoutesConfig): AnyElysia;
12
+ export type MeRouteConfig = {
13
+ jwt: JwtSignerConfig;
14
+ defaultTenantId: string;
15
+ };
16
+ export declare function createMeRoute(config: MeRouteConfig): AnyElysia;
17
+ export {};
@@ -0,0 +1,145 @@
1
+ import { Elysia, t } from "elysia";
2
+ import { createJwtSigner, createJwtVerifier } from "@longzai-intelligence-auth/jwt";
3
+ import { validatePasswordPolicy } from "@longzai-intelligence-auth/password";
4
+ import { createSessionManager } from "@longzai-intelligence-auth/session";
5
+ import { createLoginUseCase } from "@longzai-intelligence-auth/session";
6
+ import { createLogoutUseCase } from "@longzai-intelligence-auth/session";
7
+ import { createRefreshSessionUseCase } from "@longzai-intelligence-auth/session";
8
+ import { TokenMissingError, TokenInvalidError, UserAlreadyExistsError, PasswordPolicyViolationError, } from "@longzai-intelligence-auth/core";
9
+ export function createAuthRoutes(config) {
10
+ const { jwt: jwtConfig, adapter, passwordHasher, defaultTenantId } = config;
11
+ const sessionManager = createSessionManager();
12
+ let signerPromise = null;
13
+ const getTokenSigner = () => {
14
+ if (!signerPromise) {
15
+ signerPromise = createJwtSigner(jwtConfig);
16
+ }
17
+ return signerPromise;
18
+ };
19
+ let verifierPromise = null;
20
+ const getVerifier = () => {
21
+ if (!verifierPromise) {
22
+ verifierPromise = createJwtVerifier(jwtConfig);
23
+ }
24
+ return verifierPromise;
25
+ };
26
+ return new Elysia({ name: "@longzai-intelligence-auth/routes", prefix: "/auth" })
27
+ .post("/login", async ({ body, request }) => {
28
+ const { email, password } = body;
29
+ const ipAddress = request.headers.get("x-forwarded-for") ?? request.headers.get("x-real-ip") ?? undefined;
30
+ const userAgent = request.headers.get("user-agent") ?? undefined;
31
+ const tokenSigner = await getTokenSigner();
32
+ const loginUseCase = createLoginUseCase({ adapter, tokenSigner, sessionManager, defaultTenantId });
33
+ return loginUseCase.execute(email, password, userAgent, ipAddress);
34
+ }, {
35
+ body: t.Object({
36
+ email: t.String({ format: "email" }),
37
+ password: t.String({ minLength: 1 }),
38
+ }),
39
+ detail: {
40
+ summary: "用户登录",
41
+ description: "使用邮箱和密码登录,返回访问令牌和刷新令牌",
42
+ },
43
+ })
44
+ .post("/logout", async ({ request }) => {
45
+ const authHeader = request.headers.get("Authorization");
46
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
47
+ if (!bearer) {
48
+ throw new TokenMissingError();
49
+ }
50
+ const verifier = await getVerifier();
51
+ const verifyResult = await verifier.verifyAccessToken(bearer);
52
+ if (!verifyResult.success || !verifyResult.payload) {
53
+ throw new TokenInvalidError(verifyResult.error);
54
+ }
55
+ const tokenSigner = await getTokenSigner();
56
+ const logoutUseCase = createLogoutUseCase({ adapter, tokenSigner, sessionManager });
57
+ return logoutUseCase.execute("session-revoked");
58
+ }, {
59
+ detail: {
60
+ summary: "用户登出",
61
+ description: "使当前会话失效",
62
+ },
63
+ })
64
+ .post("/refresh", async ({ body }) => {
65
+ const { refreshToken } = body;
66
+ const tokenSigner = await getTokenSigner();
67
+ const refreshSessionUseCase = createRefreshSessionUseCase({ adapter, tokenSigner, sessionManager });
68
+ return refreshSessionUseCase.execute(refreshToken);
69
+ }, {
70
+ body: t.Object({
71
+ refreshToken: t.String({ minLength: 1 }),
72
+ }),
73
+ detail: {
74
+ summary: "刷新令牌",
75
+ description: "使用刷新令牌获取新的访问令牌",
76
+ },
77
+ })
78
+ .post("/register", async ({ body }) => {
79
+ const { email, password, name } = body;
80
+ const validation = validatePasswordPolicy(password);
81
+ if (!validation.valid) {
82
+ throw new PasswordPolicyViolationError(validation.errors.join(", "));
83
+ }
84
+ const existingUser = await adapter.user.findByEmail(email);
85
+ if (existingUser) {
86
+ throw new UserAlreadyExistsError();
87
+ }
88
+ const passwordHash = await passwordHasher.hash(password);
89
+ const newUser = await adapter.user.create({
90
+ email,
91
+ username: email,
92
+ passwordHash,
93
+ displayName: name,
94
+ });
95
+ return {
96
+ userId: newUser.id,
97
+ message: "注册成功",
98
+ };
99
+ }, {
100
+ body: t.Object({
101
+ email: t.String({ format: "email" }),
102
+ password: t.String({ minLength: 8 }),
103
+ name: t.String({ minLength: 1 }),
104
+ }),
105
+ detail: {
106
+ summary: "用户注册",
107
+ description: "创建新用户账户",
108
+ },
109
+ });
110
+ }
111
+ export function createMeRoute(config) {
112
+ const { jwt: jwtConfig, defaultTenantId } = config;
113
+ let verifierPromise = null;
114
+ const getVerifier = () => {
115
+ if (!verifierPromise) {
116
+ verifierPromise = createJwtVerifier(jwtConfig);
117
+ }
118
+ return verifierPromise;
119
+ };
120
+ return new Elysia({ name: "@longzai-intelligence-auth/me-route", prefix: "/auth" })
121
+ .get("/me", async ({ request }) => {
122
+ const authHeader = request.headers.get("Authorization");
123
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
124
+ if (!bearer) {
125
+ throw new TokenMissingError();
126
+ }
127
+ const verifier = await getVerifier();
128
+ const result = await verifier.verifyAccessToken(bearer);
129
+ if (!result.success || !result.payload) {
130
+ throw new TokenInvalidError(result.error);
131
+ }
132
+ const payload = result.payload;
133
+ const userId = payload.sub;
134
+ const tenantId = payload.tenantId ?? defaultTenantId;
135
+ return {
136
+ userId,
137
+ ...(tenantId ? { tenantId } : {}),
138
+ };
139
+ }, {
140
+ detail: {
141
+ summary: "获取当前用户信息",
142
+ description: "使用 Bearer Token 获取当前认证用户的信息",
143
+ },
144
+ });
145
+ }
@@ -0,0 +1,40 @@
1
+ import type { JwtSignerConfig } from "@longzai-intelligence-auth/jwt";
2
+ import type { AuthBackendPort, PasswordHasher } from "@longzai-intelligence-auth/core";
3
+ import type { LoggerService } from "@longzai-intelligence-auth/core";
4
+ export type JwtStrategyConfig = {
5
+ strategy: "jwt";
6
+ jwt: JwtSignerConfig;
7
+ defaultTenantId?: string;
8
+ };
9
+ export type StandaloneStrategyConfig = {
10
+ strategy: "standalone";
11
+ jwt: JwtSignerConfig;
12
+ adapter: AuthBackendPort;
13
+ passwordHasher: PasswordHasher;
14
+ defaultTenantId?: string;
15
+ };
16
+ export type EnvBasicStrategyConfig = {
17
+ strategy: "env-basic";
18
+ jwt: JwtSignerConfig;
19
+ adminUsername?: string;
20
+ adminPassword?: string;
21
+ defaultTenantId?: string;
22
+ };
23
+ export type AuthPluginConfig = (JwtStrategyConfig & {
24
+ registerRoutes?: boolean;
25
+ }) | (StandaloneStrategyConfig & {
26
+ registerRoutes?: boolean;
27
+ }) | (EnvBasicStrategyConfig & {
28
+ registerRoutes?: boolean;
29
+ });
30
+ export type AuthPluginOptions = AuthPluginConfig & {
31
+ logger?: LoggerService;
32
+ };
33
+ export type StrategyName = AuthPluginConfig["strategy"];
34
+ export type StrategyContext = {
35
+ strategy: StrategyName;
36
+ defaultTenantId: string;
37
+ registerRoutes: boolean;
38
+ adapter?: AuthBackendPort;
39
+ passwordHasher?: PasswordHasher;
40
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function extractClientIp(request: Request): string | undefined;
@@ -0,0 +1,5 @@
1
+ export function extractClientIp(request) {
2
+ return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
3
+ ?? request.headers.get("x-real-ip")
4
+ ?? undefined;
5
+ }
@@ -0,0 +1 @@
1
+ export declare function extractUserAgent(request: Request): string | undefined;
@@ -0,0 +1,3 @@
1
+ export function extractUserAgent(request) {
2
+ return request.headers.get("user-agent") ?? undefined;
3
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@longzai-intelligence-auth/elysia",
3
+ "version": "0.0.2",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "bun build src/index.ts --outdir dist --target bun",
30
+ "build:declaration": "tsgo --declaration --emitDeclarationOnly --outDir dist -p tsconfig/app.json",
31
+ "build:prod": "NODE_ENV=production tsdown",
32
+ "prepublishOnly": "echo skip",
33
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
34
+ "typecheck:app": "tsgo --noEmit -p tsconfig/app.json",
35
+ "typecheck:node": "tsgo --noEmit -p tsconfig/node.json",
36
+ "typecheck:test": "tsgo --noEmit -p tsconfig/test.json",
37
+ "lint": "oxlint && oxfmt --check",
38
+ "lint:fix": "oxlint --fix && oxfmt",
39
+ "test": "bun test",
40
+ "test:watch": "bun test --watch",
41
+ "test:coverage": "bun test --coverage",
42
+ "clean": "rm -rf dist out .cache"
43
+ },
44
+ "dependencies": {
45
+ "@longzai-intelligence-auth/core": "0.0.2",
46
+ "@longzai-intelligence-auth/jwt": "0.0.2",
47
+ "@longzai-intelligence-auth/rate-limit": "0.0.2",
48
+ "@longzai-intelligence-auth/rbac": "0.0.2",
49
+ "@longzai-intelligence-auth/session": "0.0.2",
50
+ "@longzai-intelligence-auth/hashing": "0.0.2",
51
+ "@longzai-intelligence/error": "^0.0.5",
52
+ "@elysiajs/bearer": "^1.3"
53
+ },
54
+ "peerDependencies": {
55
+ "elysia": "^1.3",
56
+ "@longzai-intelligence-audit/elysia": "^0.1.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@longzai-intelligence-audit/elysia": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "elysia": "^1.3",
65
+ "tsdown": "^0.22.1"
66
+ }
67
+ }