{"_id":"@access-tokens/core","_rev":"3-e16cda7df85f634599bff8277cb4e697","name":"@access-tokens/core","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@access-tokens/core","version":"1.0.0","keywords":["dynamodb","personal-access-token","pat","authentication","aws","token-management"],"author":{"name":"Trevor Robinson"},"license":"ISC","_id":"@access-tokens/core@1.0.0","maintainers":[{"name":"trevorr","email":"trevor@scurrilous.com"}],"homepage":"https://github.com/loancrate/access-tokens#readme","bugs":{"url":"https://github.com/loancrate/access-tokens/issues"},"dist":{"shasum":"a0bc3afc29ceef3de57d0c59b5d439fce8e1d30b","tarball":"https://registry.npmjs.org/@access-tokens/core/-/core-1.0.0.tgz","fileCount":15,"integrity":"sha512-d52nCGbo5zQE58zs97P2nuq/Mz7jY5hJqvsR7A3Djrpcz6YULEdtIGEjSfiuGCsM/6yDAOFu/iMZ3laFVCuBcg==","signatures":[{"sig":"MEUCICQGH6iBjl3/hs4rRp78CcMamH46r4csTt2gLx4wBuJ3AiEAzHSzuIxnTmJTl/QcRCmBzJkAcm3eLNvZx1WveSljcBY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@access-tokens%2fcore@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":67852},"main":"./dist/index.js","_from":"file:access-tokens-core-1.0.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"scripts":{"lint":"eslint src","test":"jest","build":"tsc --build tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo coverage *-junit.xml","test-int":"jest --config=jest-int.config.js","typecheck":"tsc --noEmit"},"_npmUser":{"name":"trevorr","email":"trevor@scurrilous.com"},"_resolved":"/tmp/0c4dc634c0bab9461104be763cb6fe93/access-tokens-core-1.0.0.tgz","_integrity":"sha512-d52nCGbo5zQE58zs97P2nuq/Mz7jY5hJqvsR7A3Djrpcz6YULEdtIGEjSfiuGCsM/6yDAOFu/iMZ3laFVCuBcg==","repository":{"url":"git+https://github.com/loancrate/access-tokens.git","type":"git","directory":"packages/core"},"_npmVersion":"11.6.1","description":"Personal access token library for secure token generation, verification, and lifecycle management","directories":{},"_nodeVersion":"24.11.0","dependencies":{"zod":"^4.1.12","id62":"^2.0.0","pino":"^10.1.0","@phc/format":"^1.0.0","catch-unknown":"^2.0.0","@aws-sdk/lib-dynamodb":"^3.921.0","@aws-sdk/client-dynamodb":"^3.921.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","eslint":"^9.38.0","ts-jest":"^29.4.5","jest-junit":"^16.0.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.9.2","@types/phc__format":"^1.0.1","aws-sdk-client-mock":"^4.1.0","@access-tokens/jest-config":"1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/core_1.0.0_1762480581106_0.24201102422227105","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@access-tokens/core","version":"1.1.0","description":"Personal access token library for secure token generation, verification, and lifecycle management","keywords":["dynamodb","personal-access-token","pat","authentication","aws","token-management"],"repository":{"type":"git","url":"git+https://github.com/loancrate/access-tokens.git","directory":"packages/core"},"license":"ISC","author":{"name":"Trevor Robinson"},"main":"./dist/index.js","types":"./dist/index.d.ts","dependencies":{"@aws-sdk/client-dynamodb":"^3.921.0","@aws-sdk/lib-dynamodb":"^3.921.0","@phc/format":"^1.0.0","catch-unknown":"^2.0.0","id62":"^2.0.0","pino":"^10.1.0","zod":"^4.1.12"},"devDependencies":{"@types/jest":"^30.0.0","@types/node":"^24.9.2","@types/phc__format":"^1.0.1","aws-sdk-client-mock":"^4.1.0","eslint":"^9.38.0","jest":"^30.2.0","jest-junit":"^16.0.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@access-tokens/jest-config":"1.0.1"},"engines":{"node":">=20.0.0"},"scripts":{"build":"tsc --build tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo coverage *-junit.xml","lint":"eslint src","test":"jest","test-int":"jest --config=jest-int.config.js","typecheck":"tsc --noEmit"},"_id":"@access-tokens/core@1.1.0","bugs":{"url":"https://github.com/loancrate/access-tokens/issues"},"homepage":"https://github.com/loancrate/access-tokens#readme","_integrity":"sha512-YD/aXNG3Do1BKcrhReYJaVEWYxEojbwNuLo8+2/hBliGLvgtQlC/glZH2FON4KxhVGpyPBiFwdCvqFjd2I+M9A==","_resolved":"/tmp/bf236c6be9110105792da79cad8e2189/access-tokens-core-1.1.0.tgz","_from":"file:access-tokens-core-1.1.0.tgz","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-YD/aXNG3Do1BKcrhReYJaVEWYxEojbwNuLo8+2/hBliGLvgtQlC/glZH2FON4KxhVGpyPBiFwdCvqFjd2I+M9A==","shasum":"f97ccdd824e52a0029604fbefb27a33bcc8266ea","tarball":"https://registry.npmjs.org/@access-tokens/core/-/core-1.1.0.tgz","fileCount":15,"unpackedSize":76990,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@access-tokens%2fcore@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDdK+30jHcKJiP0in43LoUjgSV39IhoAo7NdNjlQKBTtwIhAIS8aD61SPsvxY56Pz4CFa9ytN96pEMtHDoOUXyjr25p"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:0772050b-3ee6-48c2-8bc1-60f327c799bc"}},"directories":{},"maintainers":[{"name":"trevorr","email":"trevor@scurrilous.com"},{"name":"andrew-loancrate","email":"andrew@loancrate.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/core_1.1.0_1767127270931_0.4280890922947578"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-07T01:56:20.980Z","modified":"2025-12-30T20:41:11.549Z","1.0.0":"2025-11-07T01:56:21.285Z","1.1.0":"2025-12-30T20:41:11.087Z"},"bugs":{"url":"https://github.com/loancrate/access-tokens/issues"},"author":{"name":"Trevor Robinson"},"license":"ISC","homepage":"https://github.com/loancrate/access-tokens#readme","keywords":["dynamodb","personal-access-token","pat","authentication","aws","token-management"],"repository":{"type":"git","url":"git+https://github.com/loancrate/access-tokens.git","directory":"packages/core"},"description":"Personal access token library for secure token generation, verification, and lifecycle management","maintainers":[{"name":"trevorr","email":"trevor@scurrilous.com"},{"name":"andrew-loancrate","email":"andrew@loancrate.com"}],"readme":"# @access-tokens/core\n\n[![npm](https://img.shields.io/npm/v/@access-tokens/core)](https://www.npmjs.com/package/@access-tokens/core)\n[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)\n\nCore library for managing Personal Access Tokens (PATs) with secure scrypt-based hashing. Supports DynamoDB backend.\n\n## Features\n\n- **Secure Token Generation**: Cryptographically secure random token generation\n- **Scrypt Hashing**: Industry-standard password hashing with PHC format\n- **Token Lifecycle Management**: Issue, verify, update, revoke, and restore tokens\n- **Role-Based Access Control**: Attach arbitrary roles to tokens with atomic add/remove operations\n- **DynamoDB Integration**: Low-cost, scalable storage with TTL expiration\n- **TypeScript**: Full type safety with comprehensive API types\n\n## Installation\n\n```bash\nnpm install @access-tokens/core\n```\n\n## Quick Start\n\n```typescript\nimport { DynamoDBPat } from \"@access-tokens/core\";\nimport { DynamoDBDocumentClient } from \"@aws-sdk/lib-dynamodb\";\nimport { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\n\n// Initialize AWS clients\nconst dynamoClient = new DynamoDBClient({ region: \"us-east-1\" });\nconst docClient = DynamoDBDocumentClient.from(dynamoClient);\n\n// Create DynamoDBPat instance\nconst pat = new DynamoDBPat({\n  tableName: \"my-tokens\",\n  docClient,\n});\n\n// Issue a new token\nconst { token, record } = await pat.issue({\n  owner: \"user@example.com\",\n  isAdmin: false,\n  expiresAt: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60, // 1 year\n});\n\nconsole.log(\"Token:\", token); // pat_abc123...xyz\nconsole.log(\"Token ID:\", record.tokenId);\n\n// Verify a token\nconst result = await pat.verify(token);\nif (result.valid) {\n  console.log(\"Token is valid!\");\n  console.log(\"Owner:\", result.record.owner);\n  console.log(\"Is Admin:\", result.record.isAdmin);\n}\n```\n\n## API Reference\n\n### Constructor\n\n#### `new DynamoDBPat(options)`\n\nCreates a new DynamoDBPat instance.\n\n**Options:**\n\n```typescript\n{\n  tableName: string;                    // DynamoDB table name (required)\n  ddbClient?: DynamoDBClient;           // AWS DynamoDB Client (optional)\n  docClient?: DynamoDBDocumentClient;   // AWS DynamoDB Document Client (optional)\n  tokenPrefix?: string;                 // Token prefix (default: \"pat_\")\n  keyLength?: number;                   // Scrypt key length in bytes (default: 64)\n  saltLength?: number;                  // Scrypt salt length in bytes (default: 16)\n  scryptOptions?: {                     // Scrypt configuration (optional)\n    cost?: number;                      // CPU/memory cost (default: 16384)\n    blockSize?: number;                 // Block size (default: 8)\n    parallelization?: number;           // Parallelization (default: 1)\n    maxmem?: number;                    // Maximum memory (optional)\n  };\n  bootstrapPhc?: string;                // Bootstrap PHC for key derivation (optional)\n}\n```\n\n**Note:** You only need to provide one of `ddbClient` or `docClient`, not both.\nIf neither is provided, a default DynamoDB client will be created automatically.\nIf only `ddbClient` is provided, a document client will be created from it. If\n`docClient` is provided, it will be used directly.\n\n### Token Operations\n\n#### `issue(params): Promise<{ token: string; record: PatRecord }>`\n\nIssues a new token.\n\n**Parameters:**\n\n```typescript\n{\n  owner: string;           // Token owner (e.g., email address)\n  isAdmin: boolean;        // Whether token has admin privileges\n  roles?: string[];        // Array of role strings (optional, max 50 roles, 100 chars each)\n  expiresAt?: number;      // Unix timestamp for expiration (optional)\n  tokenId?: string;        // Pre-generated token ID (optional)\n}\n```\n\n**Returns:** Object containing the full token string and the database record.\n\n#### `verify(token: string): Promise<VerifyResult>`\n\nVerifies a token and returns its record if valid.\n\n**Returns:**\n\n```typescript\n{\n  valid: boolean;\n  record?: PatRecord;      // Only present if valid=true\n  reason?: string;         // Only present if valid=false\n}\n```\n\n**Reasons for invalid tokens:**\n\n- `\"invalid_prefix\"` - Token prefix doesn't match expected prefix\n- `\"invalid_format\"` - Token format is incorrect (malformed)\n- `\"not_found\"` - Token ID not found in database\n- `\"invalid_phc\"` - Stored PHC hash format is invalid\n- `\"unsupported_algorithm\"` - Hash algorithm in PHC is not supported\n- `\"invalid_parameters\"` - Scrypt parameters in PHC are invalid\n- `\"invalid_secret\"` - Secret doesn't match stored hash\n- `\"revoked\"` - Token has been revoked\n- `\"expired\"` - Token has expired\n\n#### `register(params): Promise<TokenRecord>`\n\nRegisters a token with a pre-generated ID and secret hash.\n\n**Parameters:**\n\n```typescript\n{\n  tokenId: string;         // Token ID\n  secretPhc: string;       // PHC-formatted secret hash\n  owner: string;           // Token owner\n  isAdmin: boolean;        // Admin status\n  roles?: string[];        // Array of role strings (optional)\n  expiresAt?: number;      // Expiration timestamp (optional)\n}\n```\n\n#### `update(tokenId: string, updates): Promise<void>`\n\nUpdates an existing token's properties. Supports atomic role add/remove operations.\n\n**Parameters:**\n\n- `tokenId: string` - Token ID to update\n- `updates: object` - Properties to update:\n\n```typescript\n{\n  owner?: string;          // New owner (optional)\n  isAdmin?: boolean;       // New admin status (optional)\n  roles?: RolesUpdate;     // Roles update (optional, see below)\n  secretPhc?: string;      // New secret hash (optional)\n  expiresAt?: number | null; // New expiration or null to remove (optional)\n}\n\n// RolesUpdate can be:\ntype RolesUpdate =\n  | string[]               // Replace all roles\n  | { add: string[] }      // Atomic add (cannot combine with remove)\n  | { remove: string[] }   // Atomic remove (cannot combine with add)\n```\n\n**Examples:**\n\n```typescript\n// Update basic properties\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", { owner: \"newuser@example.com\" });\n\n// Replace all roles\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", { roles: [\"reader\", \"writer\"] });\n\n// Add roles atomically (idempotent)\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", { roles: { add: [\"admin\"] } });\n\n// Remove roles atomically (idempotent)\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", { roles: { remove: [\"guest\"] } });\n\n// Clear all roles\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", { roles: [] });\n\n// Update multiple properties at once\nawait pat.update(\"34NwRzvnBbgI3uedkrQ3Q\", {\n  owner: \"newuser@example.com\",\n  roles: { add: [\"admin\"] },\n});\n```\n\n#### `revoke(tokenId: string, options?: { expiresAt?: number }): Promise<void>`\n\nRevokes a token. Optionally sets an expiration for automatic cleanup.\n\n**Example:**\n\n```typescript\n// Revoke immediately\nawait pat.revoke(\"34NwRzvnBbgI3uedkrQ3Q\");\n\n// Revoke with cleanup in 30 days\nawait pat.revoke(\"34NwRzvnBbgI3uedkrQ3Q\", {\n  expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,\n});\n```\n\n#### `restore(tokenId: string): Promise<void>`\n\nRestores a previously revoked token.\n\n**Example:**\n\n```typescript\nawait pat.restore(\"34NwRzvnBbgI3uedkrQ3Q\");\n```\n\n#### `list(options?): AsyncGenerator<PublicTokenRecord>`\n\nLists all tokens with optional filtering. Returns an async generator that yields tokens.\n\n**Options:**\n\n```typescript\n{\n  afterTokenId?: string;         // Start after this token ID (pagination)\n  limit?: number;                // Maximum tokens to return\n  includeSecretPhc?: boolean;    // Include secret hashes (default: false)\n  hasRole?: string;              // Filter tokens that have this role (optional)\n}\n```\n\n**Example:**\n\n```typescript\n// Iterate through all tokens\nfor await (const token of pat.list()) {\n  console.log(token.owner);\n}\n\n// With pagination\nfor await (const token of pat.list({\n  limit: 100,\n  afterTokenId: \"34NwRzvnBbgI3uedkrQ3Q\",\n})) {\n  console.log(token);\n}\n```\n\n**Note:** Filtering for revoked and expired tokens should be done by the caller after retrieving records.\n\n### Token Generation Utilities\n\n#### `generate(config?: { tokenId?: string }): Promise<{ token: string; tokenId: string; secretPhc: string }>`\n\nGenerates a token without storing it in the database. Useful for pre-generating tokens.\n\n**Example:**\n\n```typescript\n// Generate new token\nconst { token, tokenId, secretPhc } = await pat.generate();\n\n// Generate with specific token ID\nconst result = await pat.generate({ tokenId: \"34NwRzvnBbgI3uedkrQ3Q\" });\n```\n\n## Token Format\n\nTokens follow the format: `{prefix}{tokenId}.{secret}`\n\nExample: `pat_34NwRzvnBbgI3uedkrQ3Q.a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u8v9w0x1y2z3`\n\n- **Prefix**: Configurable (default: `pat_`)\n- **Token ID**: 21-character Base62-encoded unique identifier\n- **Secret**: Base64-encoded random bytes (default: 32 bytes = 43 Base64 chars)\n\n## Security\n\n- **Scrypt Hashing**: Secrets are hashed using scrypt with secure default parameters\n- **PHC Format**: Hashes stored in Argon2-compatible PHC string format for algorithm agility\n- **Timing-Safe Comparison**: Constant-time comparison prevents timing attacks\n- **Secure Random Generation**: Uses Node.js `crypto.randomBytes()` for token generation\n\n## DynamoDB Schema\n\n### Table\n\n**Primary Key:**\n\n- Partition Key: `tokenId` (String)\n\n**Attributes:**\n\n- `tokenId`: Unique token identifier\n- `secretPhc`: PHC-formatted secret hash\n- `owner`: Token owner identifier\n- `isAdmin`: Boolean admin flag\n- `roles`: String Set of role names (optional, stored as DynamoDB SS type)\n- `isRevoked`: Boolean revoked status\n- `expiresAt`: Unix timestamp (optional, enables TTL)\n- `createdAt`: Unix timestamp\n- `updatedAt`: Unix timestamp\n\n**Global Secondary Index (recommended):**\n\n- Index Name: `owner-index`\n- Partition Key: `owner` (String)\n- Enables efficient `listByOwner()` queries\n\n### TTL Configuration\n\nConfigure DynamoDB TTL on the `expiresAt` attribute for automatic cleanup of expired tokens.\n\n## Error Handling\n\nAll methods may throw errors. Use try-catch for error handling:\n\n```typescript\ntry {\n  const result = await pat.verify(token);\n  if (!result.valid) {\n    console.log(\"Invalid token:\", result.reason);\n  }\n} catch (error) {\n  console.error(\"Error verifying token:\", error);\n}\n```\n\n## Requirements\n\n- Node.js 20+\n- AWS SDK v3\n- DynamoDB table with appropriate permissions\n\n## Related Packages\n\n- [@access-tokens/express](https://www.npmjs.com/package/@access-tokens/express) - Express routes and middleware\n- [@access-tokens/client](https://www.npmjs.com/package/@access-tokens/client) - HTTP client for PAT API\n- [@access-tokens/cli](https://www.npmjs.com/package/@access-tokens/cli) - Command-line token management\n\n## License\n\n[ISC](https://opensource.org/licenses/ISC) © 2025 Loan Crate, Inc.\n\n## Links\n\n- [GitHub Repository](https://github.com/loancrate/access-tokens)\n- [npm Package](https://www.npmjs.com/package/@access-tokens/core)\n- [Documentation](https://github.com/loancrate/access-tokens#readme)\n","readmeFilename":"README.md"}