@lygos/nestjs-better-auth
Version:
Better Auth for NestJS
266 lines • 11.9 kB
JavaScript
;
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