{"_id":"@derek46518/nest-auth-kit","name":"@derek46518/nest-auth-kit","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@derek46518/nest-auth-kit","version":"0.1.0","description":"Reusable NestJS authentication module with local login, JWT cookies, Google OAuth, and CSRF protection","author":{"name":"Derek"},"license":"MIT","main":"dist/index.js","types":"dist/index.d.ts","peerDependencies":{"@nestjs/common":"^11.0.0","@nestjs/config":"^4.0.0","@nestjs/core":"^11.0.0","@nestjs/jwt":"^11.0.0","@nestjs/passport":"^11.0.0","class-validator":"^0.14.0","passport":"^0.7.0"},"dependencies":{"passport-google-oauth20":"^2.0.0","passport-jwt":"^4.0.1","passport-local":"^1.0.0"},"devDependencies":{"@types/express":"^4.17.21","@types/passport":"^1.0.17","@types/jest":"^29.5.12","@types/node":"^20.16.5","@types/supertest":"^2.0.16","class-validator":"^0.14.0","@nestjs/testing":"^11.0.0","@nestjs/platform-express":"^11.0.0","jest":"^29.7.0","supertest":"^6.3.4","ts-jest":"^29.2.5","typescript":"^5.6.3"},"scripts":{"build":"tsc -p tsconfig.build.json","test":"jest --config ./jest.config.js"},"_id":"@derek46518/nest-auth-kit@0.1.0","_integrity":"sha512-oJ+xRcCoJ6PwX7/X89JEm4lUDjgq+N81RownTq+7a3cfQ4iDzzZF4DiDTbWgSXj85/x5HcdRiOOKTKRjHPe+Gg==","_resolved":"/private/var/folders/k9/xrw1mzps5y7gmr0bqcbktd1w0000gn/T/85ef10ab29bdaad793214c00e27a4a34/derek46518-nest-auth-kit-0.1.0.tgz","_from":"file:derek46518-nest-auth-kit-0.1.0.tgz","_nodeVersion":"23.7.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-oJ+xRcCoJ6PwX7/X89JEm4lUDjgq+N81RownTq+7a3cfQ4iDzzZF4DiDTbWgSXj85/x5HcdRiOOKTKRjHPe+Gg==","shasum":"7d8229721ef452f40a23a8c25095bc15061d129a","tarball":"https://registry.npmjs.org/@derek46518/nest-auth-kit/-/nest-auth-kit-0.1.0.tgz","fileCount":64,"unpackedSize":448369,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAIoK/iyvjcEGD3MBrnyUtxCUHgsN7ECy8kj6rZpiYl6AiBs+PIdwMsPFgE76SvfiZ5K9w6qzHNa3UeCYrYI/2S4ig=="}]},"_npmUser":{"name":"derek46518","email":"a0977112496@gmail.com"},"directories":{},"maintainers":[{"name":"derek46518","email":"a0977112496@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-auth-kit_0.1.0_1758909261220_0.5529326995941597"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-26T17:54:21.105Z","0.1.0":"2025-09-26T17:54:21.406Z","modified":"2025-09-26T17:54:21.664Z"},"maintainers":[{"name":"derek46518","email":"a0977112496@gmail.com"}],"description":"Reusable NestJS authentication module with local login, JWT cookies, Google OAuth, and CSRF protection","author":{"name":"Derek"},"license":"MIT","readme":"# @derek46518/nest-auth-kit\n\nReusable NestJS authentication module that bundles:\n\n- Local username/email + password login\n- JWT issuance with HttpOnly cookie support\n- Google OAuth 2.0 strategy (passport-google-oauth20)\n- CSRF double-submit middleware\n- Password reset flow with pluggable token persistence\n- Optional transactional email notifications\n\nThis package extracts the auth stack used in the WebBackend project and exposes\nextensible interfaces so you can bring your own user store, reset-token\nrepository, and mailer implementation.\n\n> **Status**: experimental & work-in-progress. The API may change before 1.0.\n\n## Features\n\n- Dynamic `AuthModule` with configurable providers and cookie behaviour\n- Injectable `AuthService` exposing high-level login/registration helpers\n- Passport strategies (`local`, `jwt`, `google`) and guards ready to plug into controllers\n- CSRF middleware that validates origin + double-submit token\n- DTOs for register/forgot/reset flows using `class-validator`\n\n## Installation\n\n```bash\nnpm install @derek46518/nest-auth-kit passport passport-local passport-jwt passport-google-oauth20\n```\n\nThe package declares peer dependencies on core NestJS packages (e.g.\n`@nestjs/common`, `@nestjs/jwt`, `@nestjs/passport`). Make sure they already\nexist in your host application.\n\n## Usage overview\n\n1. **Implement adapters** that satisfy the exported interfaces.\n2. **Provide those adapters** (and optionally a mailer) in a module the kit can\n   import.\n3. **Register the auth kit** using `AuthModule.registerAsync(...)` (or\n   `register(...)`) and supply configuration (JWT, cookies, Google OAuth,\n   frontend metadata).\n4. **Expose auth endpoints** using the supplied guards/controller or by building\n   your own controllers that delegate to `AuthService`.\n\nThe following sections walk through each step.\n\n## 1. Implement adapters\n\nImplement the interfaces exported by the kit (see `src/interfaces`):\n\n- `AuthUsersService` – look up users, validate credentials, create accounts, and\n  update passwords. Must return objects `{ id, username, email | null, role }`.\n- `PasswordResetTokenStore` – persist password reset tokens (`saveToken`,\n  `findByHash`, `markUsed`).\n- `AuthMailer` *(optional)* – send transactional email. Provide\n  `isEnabled(): boolean` to indicate SMTP availability.\n\nExample Drizzle/Nest adapter:\n\n```ts\n@Injectable()\nexport class UsersAuthServiceAdapter implements AuthUsersService {\n  constructor(private readonly users: UsersService) {}\n\n  private toAuthUser(user: UsersServiceSafeUser): AuthUser {\n    return {\n      id: user.id,\n      username: user.username,\n      email: user.email,\n      role: user.role\n    };\n  }\n\n  async validateCredentials(identifier: string, password: string) {\n    const user = await this.users.validatePassword(identifier, password);\n    return user ? this.toAuthUser(user) : null;\n  }\n\n  async registerUser(username: string, password: string, email: string) {\n    return this.toAuthUser(await this.users.create(username, password, email));\n  }\n\n  async findByEmail(email: string) {\n    const user = await this.users.findByEmail(email);\n    return user ? this.toAuthUser(user as any) : null;\n  }\n\n  async createOAuthUser(email: string, usernameBase: string) {\n    return this.toAuthUser(await this.users.createOAuthUser(email, usernameBase));\n  }\n\n  async updatePassword(userId: number, newPassword: string) {\n    await this.users.updatePassword(userId, newPassword);\n  }\n}\n```\n\nPassword reset token store example:\n\n```ts\n@Injectable()\nexport class PasswordResetTokenStoreAdapter implements PasswordResetTokenStore {\n  constructor(@Inject(DRIZZLE) private readonly db: NodePgDatabase) {}\n\n  async saveToken(userId: number, tokenHash: string, expiresAt: Date) {\n    await this.db.insert(passwordResetTokens).values({ userId, tokenHash, expiresAt });\n  }\n\n  async findByHash(tokenHash: string) {\n    const [row] = await this.db\n      .select({\n        id: passwordResetTokens.id,\n        userId: passwordResetTokens.userId,\n        tokenHash: passwordResetTokens.tokenHash,\n        expiresAt: passwordResetTokens.expiresAt,\n        usedAt: passwordResetTokens.usedAt\n      })\n      .from(passwordResetTokens)\n      .where(eq(passwordResetTokens.tokenHash, tokenHash))\n      .limit(1);\n\n    if (!row) return null;\n    return {\n      id: row.id,\n      userId: row.userId,\n      tokenHash: row.tokenHash,\n      expiresAt: row.expiresAt instanceof Date ? row.expiresAt : new Date(row.expiresAt),\n      usedAt: row.usedAt ?? null\n    };\n  }\n\n  async markUsed(id: number, usedAt: Date) {\n    await this.db.update(passwordResetTokens).set({ usedAt }).where(eq(passwordResetTokens.id, id));\n  }\n}\n```\n\nIf you want transactional email support, implement `AuthMailer` (or reuse an\nexisting service) exposing:\n\n```ts\nisEnabled(): boolean;\nsend(to: string, subject: string, html: string, text?: string): Promise<void | boolean>;\n```\n\n## 2. Provide adapters & mailer in modules\n\nCreate modules that bind your adapters/mailer to the tokens exported by the kit:\n\n```ts\n@Module({\n  imports: [UsersModule],\n  providers: [\n    UsersAuthServiceAdapter,\n    PasswordResetTokenStoreAdapter,\n    { provide: AUTH_USERS_SERVICE, useExisting: UsersAuthServiceAdapter },\n    { provide: AUTH_RESET_TOKEN_STORE, useExisting: PasswordResetTokenStoreAdapter }\n  ],\n  exports: [UsersAuthServiceAdapter, PasswordResetTokenStoreAdapter, AUTH_USERS_SERVICE, AUTH_RESET_TOKEN_STORE]\n})\nexport class AuthAdaptersModule {}\n\n@Module({\n  providers: [MailService, { provide: AUTH_MAILER, useExisting: MailService }],\n  exports: [MailService, AUTH_MAILER]\n})\nexport class AuthMailerModule {}\n```\n\n## 3. Register the auth kit\n\nIn your root auth module, configure and import the kit:\n\n```ts\nconst authKitModule = AuthKitModule.registerAsync({\n  imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule],\n  useFactory: (cfg: ConfigService): AuthModuleOptions => ({\n    imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule],\n    userServiceToken: UsersAuthServiceAdapter,\n    resetTokenStoreToken: PasswordResetTokenStoreAdapter,\n    mailerToken: MailService,\n    jwt: {\n      secret: cfg.get<string>('JWT_SECRET')!,\n      expiresIn: cfg.get<string>('JWT_EXPIRES_IN') ?? '15m'\n    },\n    cookies: {\n      useCookies: cfg.get<string>('AUTH_USE_COOKIE') !== 'false',\n      cookieDomain: cfg.get<string>('COOKIE_DOMAIN') || undefined,\n      crossSite: cfg.get<string>('COOKIE_CROSS_SITE') === 'true'\n    },\n    google: cfg.get<string>('GOOGLE_CLIENT_ID') && cfg.get<string>('GOOGLE_CLIENT_SECRET')\n      ? {\n          clientID: cfg.get<string>('GOOGLE_CLIENT_ID')!,\n          clientSecret: cfg.get<string>('GOOGLE_CLIENT_SECRET')!,\n          callbackURL: cfg.get<string>('GOOGLE_CALLBACK_URL')!\n        }\n      : undefined,\n    frontend: {\n      origins: (cfg.get<string>('FRONTEND_ORIGINS') ?? '')\n        .split(',')\n        .map((s) => s.trim())\n        .filter(Boolean),\n      defaultRedirectUrl: cfg.get<string>('FRONTEND_ORIGIN') ?? 'http://localhost:3000',\n      resetUrlBase: cfg.get<string>('FRONTEND_RESET_URL') ?? ''\n    }\n  }),\n  inject: [ConfigService]\n});\n\n@Module({\n  imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule, authKitModule],\n  exports: [authKitModule, AuthAdaptersModule, AuthMailerModule]\n})\nexport class AuthModule {}\n```\n\nThe kit exports `AuthService`, strategies, guards, middleware, and DTOs. Inject\n`AuthService` wherever you need to issue tokens manually.\n\n## 4. Controllers & guards\n\nUse the supplied guards in your own controllers or expose the built-in controller\nfrom the kit. Example custom controller:\n\n```ts\n@Controller('auth')\nexport class AuthController {\n  constructor(private readonly auth: AuthService) {}\n\n  @UseGuards(LocalAuthGuard)\n  @Post('login')\n  async login(@Req() req: Request, @Res({ passthrough: true }) res: Response) {\n    const user = req.user as { id: number; username: string; role?: string | null };\n    const token = await this.auth.issueAccessToken(user);\n    res.cookie('accessToken', token, { httpOnly: true, sameSite: 'lax' });\n    return { user, accessToken: token };\n  }\n}\n```\n\n## Required environment variables\n\n| Variable | Description |\n| --- | --- |\n| `DATABASE_URL` | Connection string for your user/password-reset persistence |\n| `JWT_SECRET` | Secret used to sign access tokens (min 16 chars recommended) |\n| `JWT_EXPIRES_IN` | Token lifetime (`15m`, `1h`, etc.) |\n| `AUTH_USE_COOKIE` | `true` to set JWT in an HttpOnly cookie |\n| `COOKIE_CROSS_SITE` | `true` when API and frontend are on different domains (sets SameSite=None) |\n| `COOKIE_DOMAIN` | Optional domain for cookies (e.g. `.example.com`) |\n| `FRONTEND_ORIGIN` / `FRONTEND_ORIGINS` | Allowed origins for CORS/CSRF (comma separated) |\n| `FRONTEND_RESET_URL` | Base URL for password reset link (e.g. `https://app/reset?token=`) |\n| `GOOGLE_CLIENT_ID` | Google OAuth client ID (optional if Google login disabled) |\n| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |\n| `GOOGLE_CALLBACK_URL` | OAuth callback URL pointing back to your API |\n| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_FROM` | SMTP configuration for password reset email (optional) |\n\n## Google OAuth set-up\n\n1. In [Google Cloud Console](https://console.cloud.google.com/apis/credentials), create an OAuth client ID (Web application).\n2. Add your API callback URL (e.g. `https://api.example.com/auth/google/callback`).\n3. Add your frontend origins (`https://app.example.com`).\n4. Populate `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_CALLBACK_URL` in your environment.\n5. Expose the `/auth/google` and `/auth/google/callback` routes using the guards provided by the kit or the default controller.\n\n## Frontend integration\n\nDefault endpoints (assuming the packaged controller is used):\n\n| Method | Route | Description |\n| --- | --- | --- |\n| `POST` | `/auth/login` | Local login; returns `{ user, accessToken, csrfToken }` (cookie set when enabled) |\n| `POST` | `/auth/register` | Register a user and issue access token |\n| `POST` | `/auth/logout` | Clear cookies (if cookie mode on) |\n| `GET` | `/auth/csrf` | Obtain CSRF token for double-submit scheme |\n| `GET` | `/auth/google` | Redirect to Google OAuth |\n| `GET` | `/auth/google/callback` | Callback; redirects with state or sets cookies |\n| `POST` | `/auth/password/forgot` | Trigger password reset flow |\n| `POST` | `/auth/password/reset` | Complete password reset |\n\nFrontend tips:\n\n- When using cookies, issue requests with `credentials: 'include'`.\n- In SPA flows, parse the redirected URL for `accessToken`/`csrfToken` when\n  cookie mode is off.\n- Retrieve `/auth/csrf` before protected POST requests to include the\n  double-submit token in headers.\n\n## Testing\n\n- Unit-test your adapters to ensure they meet the contract.\n- Add e2e tests that exercise local login, Google OAuth (or stub strategy), and\n  password-reset flows.\n- Mock SMTP or the mailer when running tests to avoid external calls.\n\n## License\n\nMIT © Derek\n","readmeFilename":"README.md","_rev":"1-89d7d1d8779a9d76336249f88fa2c909"}