UNPKG

@mguay/nestjs-better-auth

Version:
472 lines (463 loc) 15.8 kB
var import_node_module = require("node:module"); var __create = Object.create; var __getProtoOf = Object.getPrototypeOf; var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __hasOwnProp = Object.prototype.hasOwnProperty; var __toESM = (mod, isNodeMode, target) => { target = mod != null ? __create(__getProtoOf(mod)) : {}; const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; for (let key of __getOwnPropNames(mod)) if (!__hasOwnProp.call(to, key)) __defProp(to, key, { get: () => mod[key], enumerable: true }); return to; }; var __moduleCache = /* @__PURE__ */ new WeakMap; var __toCommonJS = (from) => { var entry = __moduleCache.get(from), desc; if (entry) return entry; entry = __defProp({}, "__esModule", { value: true }); if (from && typeof from === "object" || typeof from === "function") __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable })); __moduleCache.set(from, entry); return entry; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: (newValue) => all[name] = () => newValue }); }; var __legacyDecorateClassTS = 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 __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index); var __legacyMetadataTS = (k, v) => { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; // src/index.ts var exports_src = {}; __export(exports_src, { Session: () => Session, Public: () => Public, Optional: () => Optional, Hook: () => Hook, HOOK_KEY: () => HOOK_KEY, BeforeHook: () => BeforeHook, BEFORE_HOOK_KEY: () => BEFORE_HOOK_KEY, AuthService: () => AuthService, AuthModule: () => AuthModule, AuthGuard: () => AuthGuard, AfterHook: () => AfterHook, AUTH_MODULE_OPTIONS_KEY: () => AUTH_MODULE_OPTIONS_KEY, AUTH_INSTANCE_KEY: () => AUTH_INSTANCE_KEY, AFTER_HOOK_KEY: () => AFTER_HOOK_KEY }); module.exports = __toCommonJS(exports_src); // src/decorators.ts var import_common = require("@nestjs/common"); // src/symbols.ts var BEFORE_HOOK_KEY = Symbol("BEFORE_HOOK"); var AFTER_HOOK_KEY = Symbol("AFTER_HOOK"); var HOOK_KEY = Symbol("HOOK"); var AUTH_INSTANCE_KEY = Symbol("AUTH_INSTANCE"); var AUTH_MODULE_OPTIONS_KEY = Symbol("AUTH_MODULE_OPTIONS"); // src/decorators.ts var Public = () => import_common.SetMetadata("PUBLIC", true); var Optional = () => import_common.SetMetadata("OPTIONAL", true); var Session = import_common.createParamDecorator((_data, context) => { const request = context.switchToHttp().getRequest(); return request.session; }); var BeforeHook = (path) => import_common.SetMetadata(BEFORE_HOOK_KEY, path); var AfterHook = (path) => import_common.SetMetadata(AFTER_HOOK_KEY, path); var Hook = () => import_common.SetMetadata(HOOK_KEY, true); // src/auth-service.ts var import_common2 = require("@nestjs/common"); class AuthService { auth; constructor(auth) { this.auth = auth; } get api() { return this.auth.api; } get instance() { return this.auth; } } AuthService = __legacyDecorateClassTS([ __legacyDecorateParamTS(0, import_common2.Inject(AUTH_INSTANCE_KEY)), __legacyMetadataTS("design:paramtypes", [ typeof T === "undefined" ? Object : T ]) ], AuthService); // src/auth-guard.ts var import_common3 = require("@nestjs/common"); var import_core = require("@nestjs/core"); var import_api = require("better-auth/api"); var import_node = require("better-auth/node"); class AuthGuard { reflector; auth; constructor(reflector, auth) { this.reflector = reflector; this.auth = auth; } async canActivate(context) { const request = context.switchToHttp().getRequest(); const session = await this.auth.api.getSession({ headers: import_node.fromNodeHeaders(request.headers) }); request.session = session; request.user = session?.user ?? null; const isPublic = this.reflector.getAllAndOverride("PUBLIC", [ context.getHandler(), context.getClass() ]); if (isPublic) return true; const isOptional = this.reflector.getAllAndOverride("OPTIONAL", [ context.getHandler(), context.getClass() ]); if (isOptional && !session) return true; if (!session) throw new import_api.APIError(401, { code: "UNAUTHORIZED", message: "Unauthorized" }); return true; } } AuthGuard = __legacyDecorateClassTS([ import_common3.Injectable(), __legacyDecorateParamTS(0, import_common3.Inject(import_core.Reflector)), __legacyDecorateParamTS(1, import_common3.Inject(AUTH_INSTANCE_KEY)), __legacyMetadataTS("design:paramtypes", [ typeof import_core.Reflector === "undefined" ? Object : import_core.Reflector, typeof Auth === "undefined" ? Object : Auth ]) ], AuthGuard); // src/auth-module.ts var import_common6 = require("@nestjs/common"); var import_core2 = require("@nestjs/core"); var import_node2 = require("better-auth/node"); var import_plugins = require("better-auth/plugins"); // src/api-error-exception-filter.ts var import_common4 = require("@nestjs/common"); var import_api2 = require("better-auth/api"); class APIErrorExceptionFilter { catch(exception, host) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = exception.statusCode; const message = exception.body?.message; response.status(status).json({ statusCode: status, message }); } } APIErrorExceptionFilter = __legacyDecorateClassTS([ import_common4.Catch(import_api2.APIError) ], APIErrorExceptionFilter); // src/middlewares.ts var import_common5 = require("@nestjs/common"); var express = __toESM(require("express")); class SkipBodyParsingMiddleware { use(req, res, next) { if (req.baseUrl.startsWith("/api/auth")) { next(); return; } express.json()(req, res, (err) => { if (err) { next(err); return; } express.urlencoded({ extended: true })(req, res, next); }); } } SkipBodyParsingMiddleware = __legacyDecorateClassTS([ import_common5.Injectable() ], SkipBodyParsingMiddleware); // src/auth-module.ts var HOOKS = [ { metadataKey: BEFORE_HOOK_KEY, hookType: "before" }, { metadataKey: AFTER_HOOK_KEY, hookType: "after" } ]; class AuthModule { auth; discoveryService; metadataScanner; adapter; options; logger = new import_common6.Logger(AuthModule.name); constructor(auth, discoveryService, metadataScanner, adapter, options) { this.auth = auth; this.discoveryService = discoveryService; this.metadataScanner = metadataScanner; this.adapter = adapter; this.options = options; } onModuleInit() { const hooks = this.auth.hooks || this.auth.options?.hooks; if (!hooks) return; const providers = this.discoveryService.getProviders().filter(({ metatype }) => metatype && Reflect.getMetadata(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) { const trustedOrigins = this.auth.options.trustedOrigins; 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(SkipBodyParsingMiddleware).forRoutes("*path"); let basePath = this.auth.options.basePath ?? "/api/auth"; if (!basePath.startsWith("/")) { basePath = `/${basePath}`; } if (basePath.endsWith("/")) { basePath = basePath.slice(0, -1); } const handler = import_node2.toNodeHandler(this.auth); this.adapter.httpAdapter.getInstance().use(`${basePath}/*path`, (req, res) => { req.url = req.originalUrl; return handler(req, res); }); this.logger.log(`AuthModule initialized BetterAuth on '${basePath}/*'`); } setupHooks(providerMethod, providerClass) { const hooks = this.auth.hooks || this.auth.options?.hooks; if (!hooks) return; for (const { metadataKey, hookType } of HOOKS) { const hookPath = Reflect.getMetadata(metadataKey, providerMethod); if (!hookPath) continue; const originalHook = hooks[hookType]; hooks[hookType] = import_plugins.createAuthMiddleware(async (ctx) => { if (originalHook) { await originalHook(ctx); } if (hookPath === ctx.path) { await providerMethod.apply(providerClass, [ctx]); } }); } } static forRoot(auth, options = {}) { const authAny = auth; if (authAny.hooks !== undefined) { authAny.hooks = { ...authAny.hooks }; } else if (authAny.options) { authAny.options.hooks = { ...authAny.options.hooks }; } const providers = [ { provide: AUTH_INSTANCE_KEY, useValue: auth }, { provide: AUTH_MODULE_OPTIONS_KEY, useValue: options }, AuthService ]; if (!options.disableExceptionFilter) { providers.push({ provide: import_core2.APP_FILTER, useClass: APIErrorExceptionFilter }); } return { global: true, module: AuthModule, providers, exports: [ { provide: AUTH_INSTANCE_KEY, useValue: auth }, { provide: AUTH_MODULE_OPTIONS_KEY, useValue: options }, AuthService ] }; } static forRootAsync(options) { const asyncProviders = AuthModule.createAsyncProviders(options); return { global: true, module: AuthModule, imports: options.imports || [], providers: [...asyncProviders, AuthService], exports: [ { provide: AUTH_INSTANCE_KEY, useExisting: AUTH_INSTANCE_KEY }, { provide: AUTH_MODULE_OPTIONS_KEY, useExisting: AUTH_MODULE_OPTIONS_KEY }, AuthService ] }; } static createAsyncProviders(options) { if (options.useFactory) { return [ { provide: AUTH_INSTANCE_KEY, useFactory: async (...args) => { const result = await options.useFactory?.(...args); const auth = result.auth; const authAny = auth; if (authAny.hooks !== undefined) { authAny.hooks = { ...authAny.hooks }; } else if (authAny.options) { authAny.options.hooks = { ...authAny.options.hooks }; } return auth; }, inject: options.inject || [] }, { provide: AUTH_MODULE_OPTIONS_KEY, useFactory: async (...args) => { const result = await options.useFactory?.(...args); return result.options || {}; }, inject: options.inject || [] }, AuthModule.createExceptionFilterProvider() ]; } if (options.useClass) { return [ { provide: options.useClass, useClass: options.useClass }, { provide: AUTH_INSTANCE_KEY, useFactory: async (configService) => { const result = await configService.createAuthOptions(); const auth = result.auth; const authAny = auth; if (authAny.hooks !== undefined) { authAny.hooks = { ...authAny.hooks }; } else if (authAny.options) { authAny.options.hooks = { ...authAny.options.hooks }; } return auth; }, inject: [options.useClass] }, { provide: AUTH_MODULE_OPTIONS_KEY, useFactory: async (configService) => { const result = await configService.createAuthOptions(); return result.options || {}; }, inject: [options.useClass] }, AuthModule.createExceptionFilterProvider() ]; } if (options.useExisting) { return [ { provide: AUTH_INSTANCE_KEY, useFactory: async (configService) => { const result = await configService.createAuthOptions(); const auth = result.auth; const authAny = auth; if (authAny.hooks !== undefined) { authAny.hooks = { ...authAny.hooks }; } else if (authAny.options) { authAny.options.hooks = { ...authAny.options.hooks }; } return auth; }, inject: [options.useExisting] }, { provide: AUTH_MODULE_OPTIONS_KEY, useFactory: async (configService) => { const result = await configService.createAuthOptions(); return result.options || {}; }, inject: [options.useExisting] }, AuthModule.createExceptionFilterProvider() ]; } throw new Error("Invalid async configuration. Must provide useFactory, useClass, or useExisting."); } static createExceptionFilterProvider() { return { provide: import_core2.APP_FILTER, useFactory: (options) => { return options.disableExceptionFilter ? null : new APIErrorExceptionFilter; }, inject: [AUTH_MODULE_OPTIONS_KEY] }; } } AuthModule = __legacyDecorateClassTS([ import_common6.Module({ imports: [import_core2.DiscoveryModule] }), __legacyDecorateParamTS(0, import_common6.Inject(AUTH_INSTANCE_KEY)), __legacyDecorateParamTS(1, import_common6.Inject(import_core2.DiscoveryService)), __legacyDecorateParamTS(2, import_common6.Inject(import_core2.MetadataScanner)), __legacyDecorateParamTS(3, import_common6.Inject(import_core2.HttpAdapterHost)), __legacyDecorateParamTS(4, import_common6.Inject(AUTH_MODULE_OPTIONS_KEY)), __legacyMetadataTS("design:paramtypes", [ Object, typeof import_core2.DiscoveryService === "undefined" ? Object : import_core2.DiscoveryService, typeof import_core2.MetadataScanner === "undefined" ? Object : import_core2.MetadataScanner, typeof import_core2.HttpAdapterHost === "undefined" ? Object : import_core2.HttpAdapterHost, typeof AuthModuleOptions === "undefined" ? Object : AuthModuleOptions ]) ], AuthModule);