UNPKG

@lygos/nestjs-better-auth

Version:
266 lines 11.9 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var AuthModule_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthModule = void 0; const common_1 = require("@nestjs/common"); const core_1 = require("@nestjs/core"); const node_1 = require("better-auth/node"); const plugins_1 = require("better-auth/plugins"); const api_error_exception_filter_1 = require("./api-error-exception-filter"); const auth_service_1 = require("./auth-service"); const middlewares_1 = require("./middlewares"); const symbols_1 = require("./symbols"); const HOOKS = [ { metadataKey: symbols_1.BEFORE_HOOK_KEY, hookType: "before" }, { metadataKey: symbols_1.AFTER_HOOK_KEY, hookType: "after" }, ]; /** * NestJS module that integrates the Auth library with NestJS applications. * Provides authentication middleware, hooks, and exception handling. */ let AuthModule = AuthModule_1 = class AuthModule { constructor(auth, discoveryService, metadataScanner, adapter, options) { this.auth = auth; this.discoveryService = discoveryService; this.metadataScanner = metadataScanner; this.adapter = adapter; this.options = options; this.logger = new common_1.Logger(AuthModule_1.name); } onModuleInit() { // Setup hooks if (!this.auth.options.hooks) return; const providers = this.discoveryService .getProviders() .filter(({ metatype }) => metatype && Reflect.getMetadata(symbols_1.HOOK_KEY, metatype)); for (const provider of providers) { const providerPrototype = Object.getPrototypeOf(provider.instance); const methods = this.metadataScanner.getAllMethodNames(providerPrototype); for (const method of methods) { const providerMethod = providerPrototype[method]; this.setupHooks(providerMethod, provider.instance); } } } configure(consumer) { var _a; const trustedOrigins = this.auth.options.trustedOrigins; // function-based trustedOrigins requires a Request (from web-apis) object to evaluate, which is not available in NestJS (we only have a express Request object) // if we ever need this, take a look at better-call which show an implementation for this const isNotFunctionBased = trustedOrigins && Array.isArray(trustedOrigins); if (!this.options.disableTrustedOriginsCors && isNotFunctionBased) { this.adapter.httpAdapter.enableCors({ origin: trustedOrigins, methods: ["GET", "POST", "PUT", "DELETE"], credentials: true, }); } else if (trustedOrigins && !this.options.disableTrustedOriginsCors && !isNotFunctionBased) throw new Error("Function-based trustedOrigins not supported in NestJS. Use string array or disable CORS with disableTrustedOriginsCors: true."); if (!this.options.disableBodyParser) consumer.apply(middlewares_1.SkipBodyParsingMiddleware).forRoutes("*path"); // Get basePath from options or use default let basePath = (_a = this.auth.options.basePath) !== null && _a !== void 0 ? _a : "/api/auth"; // Ensure basePath starts with / if (!basePath.startsWith("/")) { basePath = `/${basePath}`; } // Ensure basePath doesn't end with / if (basePath.endsWith("/")) { basePath = basePath.slice(0, -1); } const handler = (0, node_1.toNodeHandler)(this.auth); this.adapter.httpAdapter .getInstance() // little hack to ignore any global prefix // for now i'll just not support a global prefix .use(`${basePath}/*path`, (req, res) => { req.url = req.originalUrl; return handler(req, res); }); this.logger.log(`AuthModule initialized BetterAuth on '${basePath}/*'`); } setupHooks(providerMethod, providerClass) { if (!this.auth.options.hooks) return; for (const { metadataKey, hookType } of HOOKS) { const hookPath = Reflect.getMetadata(metadataKey, providerMethod); if (!hookPath) continue; const originalHook = this.auth.options.hooks[hookType]; this.auth.options.hooks[hookType] = (0, plugins_1.createAuthMiddleware)(async (ctx) => { if (originalHook) { await originalHook(ctx); } if (hookPath === ctx.path) { await providerMethod.apply(providerClass, [ctx]); } }); } } /** * Static factory method to create and configure the AuthModule. * @param auth - The Auth instance to use * @param options - Configuration options for the module */ static forRoot( // biome-ignore lint/suspicious/noExplicitAny: i still need to find a type for the auth instance auth, options = {}) { // Initialize hooks with an empty object if undefined // Without this initialization, the setupHook method won't be able to properly override hooks // It won't throw an error, but any hook functions we try to add won't be called auth.options.hooks = { ...auth.options.hooks, }; const providers = [ { provide: symbols_1.AUTH_INSTANCE_KEY, useValue: auth, }, { provide: symbols_1.AUTH_MODULE_OPTIONS_KEY, useValue: options, }, auth_service_1.AuthService, ]; if (!options.disableExceptionFilter) { providers.push({ provide: core_1.APP_FILTER, useClass: api_error_exception_filter_1.APIErrorExceptionFilter, }); } return { global: true, module: AuthModule_1, providers: providers, exports: [ { provide: symbols_1.AUTH_INSTANCE_KEY, useValue: auth, }, { provide: symbols_1.AUTH_MODULE_OPTIONS_KEY, useValue: options, }, auth_service_1.AuthService, ], }; } /** * Static factory method to create and configure the AuthModule asynchronously. * Useful when you need to inject dependencies or fetch configuration from external sources. * @param asyncOptions - Async configuration options */ static forRootAsync(asyncOptions) { const providers = this.createAsyncProviders(asyncOptions); return { global: true, module: AuthModule_1, imports: asyncOptions.imports || [], providers: [...providers, auth_service_1.AuthService], exports: [symbols_1.AUTH_INSTANCE_KEY, symbols_1.AUTH_MODULE_OPTIONS_KEY, auth_service_1.AuthService], }; } /** * Creates the async providers for the AuthModule */ static createAsyncProviders(options) { var _a; const providers = []; if ("useFactory" in options) { // Factory-based configuration const authProvider = { provide: symbols_1.AUTH_INSTANCE_KEY, useFactory: async (...args) => { const auth = await options.useFactory(...args); // Initialize hooks with an empty object if undefined // Without this initialization, the setupHook method won't be able to properly override hooks // It won't throw an error, but any hook functions we try to add won't be called auth.options.hooks = { ...auth.options.hooks, }; return auth; }, inject: options.inject || [], }; const optionsProvider = { provide: symbols_1.AUTH_MODULE_OPTIONS_KEY, useValue: options.options || {}, }; providers.push(authProvider, optionsProvider); // Add exception filter if not disabled if (!((_a = options.options) === null || _a === void 0 ? void 0 : _a.disableExceptionFilter)) { providers.push({ provide: core_1.APP_FILTER, useClass: api_error_exception_filter_1.APIErrorExceptionFilter, }); } } else if ("useClass" in options) { // Class-based configuration const factoryProvider = { provide: symbols_1.AUTH_INSTANCE_KEY, useFactory: async (optionsFactory) => { const config = await optionsFactory.createAuthModuleOptions(); // Initialize hooks with an empty object if undefined config.auth.options.hooks = { ...config.auth.options.hooks, }; return config.auth; }, inject: [options.useClass], }; const optionsFactoryProvider = { provide: symbols_1.AUTH_MODULE_OPTIONS_KEY, useFactory: async (optionsFactory) => { const config = await optionsFactory.createAuthModuleOptions(); return config.options || {}; }, inject: [options.useClass], }; const classProvider = { provide: options.useClass, useClass: options.useClass, }; providers.push(factoryProvider, optionsFactoryProvider, classProvider); // For class-based config, we'll add the exception filter by default // Users can still disable it by returning disableExceptionFilter: true in their factory // This is simpler than trying to conditionally register providers providers.push({ provide: core_1.APP_FILTER, useClass: api_error_exception_filter_1.APIErrorExceptionFilter, }); } return providers; } }; exports.AuthModule = AuthModule; exports.AuthModule = AuthModule = AuthModule_1 = __decorate([ (0, common_1.Module)({ imports: [core_1.DiscoveryModule], }), __param(0, (0, common_1.Inject)(symbols_1.AUTH_INSTANCE_KEY)), __param(1, (0, common_1.Inject)(core_1.DiscoveryService)), __param(2, (0, common_1.Inject)(core_1.MetadataScanner)), __param(3, (0, common_1.Inject)(core_1.HttpAdapterHost)), __param(4, (0, common_1.Inject)(symbols_1.AUTH_MODULE_OPTIONS_KEY)), __metadata("design:paramtypes", [Object, core_1.DiscoveryService, core_1.MetadataScanner, core_1.HttpAdapterHost, Object]) ], AuthModule); //# sourceMappingURL=auth-module.js.map