{"_id":"@_bisht_akash/sentra","name":"@_bisht_akash/sentra","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@_bisht_akash/sentra","version":"1.0.0","description":"Authentication and authorization framework for TypeScript applications","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"dev":"tsx src/index.ts","build":"tsup","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run test && npm run build"},"keywords":["authentication","authorization","jwt","typescript","auth"],"author":"","devDependencies":{"@types/bcrypt":"^6.0.0","tsup":"^8.5.1","tsx":"^4.23.12","typescript":"^5.7.3","vitest":"^4.1.10"},"dependencies":{"bcrypt":"^6.0.0","jose":"^6.2.9"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"license":"ISC","publishConfig":{"access":"public"},"_id":"@_bisht_akash/sentra@1.0.0","gitHead":"4cdbf745129903c5899dab23d674fee41df462bb","_nodeVersion":"22.5.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-kYQ0/Zc1mcO26ayd5LaZVQe0ZQehACh12KQdUMjIvOQOr38QEVGNzVp6xwQXYf+gtrpG1Wpuw2we/vEUi+GSCA==","shasum":"c289fba453170670534f04a4ef916d3763490440","tarball":"https://registry.npmjs.org/@_bisht_akash/sentra/-/sentra-1.0.0.tgz","fileCount":6,"unpackedSize":38326,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHoFSs7C6QVc7WOhk5bgqZwtUBOqvfXqs3bCSQajdDJoAiAYcRuc/BTfQXULT1RGYHlUrEiUIwK3HBIkSQFJ97nQjw=="}]},"_npmUser":{"name":"_bisht_akash","email":"bisht26akash@gmail.com"},"directories":{},"maintainers":[{"name":"_bisht_akash","email":"bisht26akash@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sentra_1.0.0_1787240115098_0.7220609513423708"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T15:35:14.923Z","1.0.0":"2026-08-20T15:35:15.274Z","modified":"2026-08-20T15:35:15.487Z"},"maintainers":[{"name":"_bisht_akash","email":"bisht26akash@gmail.com"}],"description":"Authentication and authorization framework for TypeScript applications","keywords":["authentication","authorization","jwt","typescript","auth"],"license":"ISC","readme":"# Sentra v1.0.0\r\n\r\nSentra is a lightweight, database-agnostic authentication and authorization mechanics engine for TypeScript and JavaScript applications. Sentra handles secure password hashing, JWT creation and validation, and advanced refresh token rotation (RTR) with automatic reuse detection—allowing you to focus purely on your application's business rules.\r\n\r\n```\r\n       Frontend (Next.js, React, Mobile, etc.)\r\n                          │\r\n                          │ credentials (email + password)\r\n                          ▼\r\n                  Developer's Backend\r\n                          │\r\n                          ▼\r\n                       SENTRA\r\n                          │\r\n     ┌────────────────────┼────────────────────┐\r\n     ▼                    ▼                    ▼\r\nPassword Hashing   JWT Token Mgmt     Session & Token Rotation\r\n(bcrypt HS256)      (jose HS256)      (Reuse Detection / Hooks)\r\n     ▲                    ▲                    ▲\r\n     └────────────────────┼────────────────────┘\r\n                          │\r\n                          ▼\r\n             Adapters (User & Session)\r\n                          │\r\n                          ▼\r\n                Developer's Database\r\n```\r\n\r\n---\r\n\r\n## Table of Contents\r\n\r\n- [Installation](#installation)\r\n- [Quick Start](#quick-start)\r\n- [Adapter Documentation](#adapter-documentation)\r\n  - [UserAdapter](#useradapter)\r\n  - [RefreshTokenAdapter](#refreshtokenadapter)\r\n  - [Concrete Prisma Example](#concrete-prisma-example)\r\n- [Refresh-Token Architecture](#refresh-token-architecture)\r\n  - [How Token Rotation Works](#how-token-rotation-works)\r\n  - [Automatic Reuse Detection](#automatic-reuse-detection)\r\n- [Hooks](#hooks)\r\n- [Errors](#errors)\r\n- [API Reference](#api-reference)\r\n  - [createAuth](#createauth)\r\n  - [Auth Class](#auth-class)\r\n\r\n---\r\n\r\n## Installation\r\n\r\nInstall Sentra via npm, yarn, or pnpm:\r\n\r\n```bash\r\n# npm\r\nnpm install sentra\r\n\r\n# yarn\r\nyarn add sentra\r\n\r\n# pnpm\r\npnpm add sentra\r\n```\r\n\r\n---\r\n\r\n## Quick Start\r\n\r\nInitialize Sentra by providing your custom database adapters and configuration secret:\r\n\r\n```typescript\r\nimport { createAuth } from 'sentra';\r\nimport { MyDatabaseAdapter } from './my-database-adapter'; // Custom implementation\r\n\r\n// 1. Initialize Auth Engine\r\nconst dbAdapter = new MyDatabaseAdapter();\r\nexport const auth = createAuth({\r\n  adapter: dbAdapter,\r\n  refreshTokenAdapter: dbAdapter,\r\n  secret: process.env.JWT_SECRET || 'your-super-secret-key',\r\n  tokenExpiry: '15m',        // Access token expiry (e.g. 15m, 1h, 7d)\r\n  refreshTokenExpiry: '30d', // Refresh token expiry\r\n});\r\n\r\n// 2. Sign Up a User\r\nconst newUser = await auth.signUp({\r\n  email: 'user@example.com',\r\n  password: 'securepassword123',\r\n});\r\n\r\n// 3. Log In a User\r\nconst { user, token, refreshToken } = await auth.login({\r\n  email: 'user@example.com',\r\n  password: 'securepassword123',\r\n});\r\n// Sentra returns:\r\n// - `user`: The user's metadata (id, email)\r\n// - `token`: A short-lived JWT access token\r\n// - `refreshToken`: A secure, single-use refresh token string\r\n\r\n// 4. Authenticate a Request\r\nconst authenticatedUser = await auth.authenticate(token);\r\n\r\n// 5. Refresh Tokens\r\nconst tokens = await auth.refresh(refreshToken);\r\n// Returns a new access token and a rotated refresh token\r\n```\r\n\r\n---\r\n\r\n## Adapter Documentation\r\n\r\nSentra is completely database-agnostic. To wire it up with your database (PostgreSQL, MongoDB, MySQL, Redis, etc.), you implement two TypeScript interfaces: `UserAdapter` and `RefreshTokenAdapter`.\r\n\r\n### UserAdapter\r\n\r\nHandles operations related to the user accounts.\r\n\r\n```typescript\r\nexport interface User {\r\n  id: string;\r\n  email: string;\r\n}\r\n\r\nexport interface UserRecord extends User {\r\n  passwordHash: string;\r\n}\r\n\r\nexport interface CreateUser {\r\n  email: string;\r\n  passwordHash: string;\r\n}\r\n\r\nexport interface UserAdapter {\r\n  findUserByEmail(email: string): Promise<UserRecord | null>;\r\n  findUserById(userId: string): Promise<UserRecord | null>;\r\n  createUser(data: CreateUser): Promise<UserRecord>;\r\n}\r\n```\r\n\r\n### RefreshTokenAdapter\r\n\r\nHandles storage and state tracking for refresh token sessions to power the Refresh Token Rotation (RTR) mechanics.\r\n\r\n```typescript\r\nexport interface RefreshSession {\r\n  sessionId: string;\r\n  familyId: string;\r\n  userId: string;\r\n  refreshTokenHash: string;\r\n  expiresAt: Date;\r\n  revokedAt: Date | null;\r\n}\r\n\r\nexport interface RefreshTokenAdapter {\r\n  findSessionByTokenHash(refreshTokenHash: string): Promise<RefreshSession | null>;\r\n  createSession(session: RefreshSession): Promise<RefreshSession>;\r\n  revokeSession(sessionId: string): Promise<void>;\r\n  revokeFamily(familyId: string): Promise<void>;\r\n}\r\n```\r\n\r\n### Concrete Prisma Example\r\n\r\nHere is a full example of implementing both interfaces using **Prisma ORM**:\r\n\r\n#### Prisma Schema\r\n\r\n```prisma\r\nmodel User {\r\n  id           String           @id @default(uuid())\r\n  email        String           @unique\r\n  passwordHash String\r\n  sessions     RefreshSession[]\r\n}\r\n\r\nmodel RefreshSession {\r\n  sessionId        String    @id\r\n  familyId         String\r\n  userId           String\r\n  refreshTokenHash String    @unique\r\n  expiresAt        DateTime\r\n  revokedAt        DateTime?\r\n  user             User      @relation(fields: [userId], references: [id], onDelete: Cascade)\r\n}\r\n```\r\n\r\n#### Adapter Class\r\n\r\n```typescript\r\nimport { PrismaClient } from '@prisma/client';\r\nimport type { \r\n  UserAdapter, \r\n  RefreshTokenAdapter, \r\n  UserRecord, \r\n  CreateUser, \r\n  RefreshSession \r\n} from 'sentra';\r\n\r\nconst prisma = new PrismaClient();\r\n\r\nexport class SentraDbAdapter implements UserAdapter, RefreshTokenAdapter {\r\n  // --- UserAdapter Implementation ---\r\n\r\n  async findUserByEmail(email: string): Promise<UserRecord | null> {\r\n    return prisma.user.findUnique({ where: { email } });\r\n  }\r\n\r\n  async findUserById(userId: string): Promise<UserRecord | null> {\r\n    return prisma.user.findUnique({ where: { id: userId } });\r\n  }\r\n\r\n  async createUser(data: CreateUser): Promise<UserRecord> {\r\n    return prisma.user.create({\r\n      data: {\r\n        email: data.email,\r\n        passwordHash: data.passwordHash,\r\n      },\r\n    });\r\n  }\r\n\r\n  // --- RefreshTokenAdapter Implementation ---\r\n\r\n  async findSessionByTokenHash(refreshTokenHash: string): Promise<RefreshSession | null> {\r\n    return prisma.refreshSession.findUnique({ where: { refreshTokenHash } });\r\n  }\r\n\r\n  async createSession(session: RefreshSession): Promise<RefreshSession> {\r\n    return prisma.refreshSession.create({ data: session });\r\n  }\r\n\r\n  async revokeSession(sessionId: string): Promise<void> {\r\n    await prisma.refreshSession.update({\r\n      where: { sessionId },\r\n      data: { revokedAt: new Date() },\r\n    });\r\n  }\r\n\r\n  async revokeFamily(familyId: string): Promise<void> {\r\n    await prisma.refreshSession.updateMany({\r\n      where: { familyId },\r\n      data: { revokedAt: new Date() },\r\n    });\r\n  }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Refresh-Token Architecture\r\n\r\nSentra implements a highly secure refresh-token structure utilizing **Refresh Token Rotation (RTR)** to protect against token hijacking.\r\n\r\n```\r\n🔑 LOGIN  ──> Generates Token + Refresh Token (R1) (belongs to Family F1)\r\n                │\r\n🔄 REFRESH ──> Presents R1 ──> Sentra revokes R1 ──> Generates Token + R2 (Family F1)\r\n                │\r\n⚠️ REUSE   ──> Thief presents revoked R1 ──> Sentra detects breach ──> Revokes ENTIRE Family F1\r\n```\r\n\r\n### How Token Rotation Works\r\n\r\n1. When a user logs in, a refresh token is generated along with a unique `sessionId` and a `familyId` (representing the chain of refreshes in this login session).\r\n2. The refresh token string is cryptographically hashed (SHA-256) before storing it in the database to prevent database-compromise token extraction.\r\n3. Upon refreshing, the old refresh token is marked as `revokedAt = new Date()`, and a brand new refresh token is issued to the client under the same `familyId`.\r\n\r\n### Automatic Reuse Detection\r\n\r\nIf an attacker steals a refresh token and uses it:\r\n- Either the **victim** or the **attacker** will attempt to refresh the token first, causing the token to be marked as revoked.\r\n- When the second party attempts to use that same (already revoked) refresh token, Sentra's reuse detection triggers.\r\n- Sentra automatically calls `revokeFamily(familyId)`, instantly invalidating **every single refresh token** in that family chain.\r\n- The next time the user or attacker makes an authenticated request with an expired access token, they will be blocked and forced to re-authenticate completely.\r\n\r\n---\r\n\r\n## Hooks\r\n\r\nYou can define optional lifecycle hooks to execute side effects during key events:\r\n\r\n```typescript\r\nexport interface AuthHooks {\r\n  beforeSignUp?: (data: { email: string }) => Promise<void>;\r\n  afterSignUp?: (user: User) => Promise<void>;\r\n  beforeLogin?: (user: User) => Promise<void>;\r\n  afterLogin?: (user: User) => Promise<void>;\r\n}\r\n```\r\n\r\n### Example Usage\r\n\r\n```typescript\r\nconst auth = createAuth({\r\n  adapter,\r\n  refreshTokenAdapter,\r\n  secret: 'my-secret',\r\n  hooks: {\r\n    beforeSignUp: async ({ email }) => {\r\n      if (email.endsWith('@disallowed.com')) {\r\n        throw new Error('Disallowed email domain.');\r\n      }\r\n    },\r\n    afterSignUp: async (user) => {\r\n      await sendWelcomeEmail(user.email);\r\n    },\r\n    beforeLogin: async (user) => {\r\n      // Implement account suspension or verification check\r\n      const isSuspended = await checkSuspensionStatus(user.id);\r\n      if (isSuspended) throw new Error('Account is suspended.');\r\n    },\r\n    afterLogin: async (user) => {\r\n      await logAuditActivity(user.id, 'user_login_success');\r\n    }\r\n  }\r\n});\r\n```\r\n\r\n> [!NOTE]\r\n> Errors thrown inside `beforeSignUp` and `beforeLogin` hooks will abort the operation. If hooks like `afterSignUp` or `afterLogin` throw, they are caught and logged automatically to prevent breaking the core user response flow.\r\n\r\n---\r\n\r\n## Errors\r\n\r\nSentra throws a custom `AuthError` containing a descriptive error message and an error code to make error handling clean.\r\n\r\n### Error Codes\r\n\r\n- `USER_ALREADY_EXISTS`: Thrown during signup if a user record with the same email already exists.\r\n- `INVALID_CREDENTIALS`: Thrown during login if the email is not found or the password comparison fails.\r\n- `AUTHENTICATION_FAILED`: Thrown during token validation or refresh flow (e.g. invalid tokens, expired tokens, or token reuse detection).\r\n\r\n### Example Error Handling\r\n\r\n```typescript\r\nimport { AuthError } from 'sentra';\r\n\r\ntry {\r\n  const result = await auth.login({ email, password });\r\n} catch (error) {\r\n  if (error instanceof AuthError) {\r\n    switch (error.code) {\r\n      case 'INVALID_CREDENTIALS':\r\n        res.status(401).json({ error: 'Invalid email or password' });\r\n        break;\r\n      case 'AUTHENTICATION_FAILED':\r\n        res.status(403).json({ error: 'Session expired or invalidated' });\r\n        break;\r\n      default:\r\n        res.status(500).json({ error: error.message });\r\n    }\r\n  } else {\r\n    res.status(500).json({ error: 'Internal server error' });\r\n  }\r\n}\r\n```\r\n\r\n---\r\n\r\n## API Reference\r\n\r\n### `createAuth(config)`\r\n\r\nFactory function to create a new `Auth` instance.\r\n\r\n#### Parameters\r\n\r\n- `config: AuthConfig`\r\n  - `adapter: UserAdapter` (Required) - Database adapter for user operations.\r\n  - `refreshTokenAdapter: RefreshTokenAdapter` (Required) - Database adapter for refresh token sessions.\r\n  - `secret: string` (Required) - HMAC secret key for signing JWTs.\r\n  - `tokenExpiry?: string` (Optional) - Expiration duration for access tokens (e.g., `\"15m\"`, `\"1h\"`, `\"7d\"`). Defaults to `\"7d\"`.\r\n  - `refreshTokenExpiry?: string` (Optional) - Expiration duration for refresh tokens (e.g., `\"30d\"`, `\"90d\"`). Defaults to `\"30d\"`.\r\n  - `hooks?: AuthHooks` (Optional) - Lifecycle hooks object.\r\n\r\n#### Returns\r\n\r\nAn instance of the `Auth` class.\r\n\r\n---\r\n\r\n### `Auth` Class\r\n\r\n#### `signUp(data)`\r\n\r\nCreates a new user record. Hashes the password with bcrypt (cost factor of 10) before saving.\r\n\r\n- **Parameters**: `data: SignUpData` (`{ email, password }`)\r\n- **Returns**: `Promise<User>` (`{ id, email }`)\r\n- **Throws**: `AuthError` (code: `USER_ALREADY_EXISTS`)\r\n\r\n#### `login(data)`\r\n\r\nValidates credentials, creates a refresh token family/session, and returns JWT tokens.\r\n\r\n- **Parameters**: `data: LoginData` (`{ email, password }`)\r\n- **Returns**: `Promise<AuthResult>` (`{ user: { id, email }, token, refreshToken }`)\r\n- **Throws**: `AuthError` (code: `INVALID_CREDENTIALS`)\r\n\r\n#### `authenticate(token)`\r\n\r\nVerifies a short-lived access token and retrieves the associated user.\r\n\r\n- **Parameters**: `token: string` - The access token JWT.\r\n- **Returns**: `Promise<User>` (`{ id, email }`)\r\n- **Throws**: `AuthError` (code: `AUTHENTICATION_FAILED`)\r\n\r\n#### `refresh(refreshToken)`\r\n\r\nValidates the refresh token, executes rotation, generates a new token/refresh token pair, and checks for reuse.\r\n\r\n- **Parameters**: `refreshToken: string` - The single-use refresh token.\r\n- **Returns**: `Promise<AuthResult>` (`{ user: { id, email }, token, refreshToken }`)\r\n- **Throws**: `AuthError` (code: `AUTHENTICATION_FAILED`)\r\n","readmeFilename":"README.md","_rev":"1-b9f7da384362158cc14edc6eb03de784"}