{"_id":"@canxjs/citadel","name":"@canxjs/citadel","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@canxjs/citadel","version":"1.0.0","type":"module","description":"CanxJS Citadel - Secure API Token Authentication","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc"},"keywords":["canxjs","auth","sanctum","api","token"],"author":{"name":"CanxJS Team"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/chandafa/canxjs.git","directory":"packages/citadel"},"publishConfig":{"access":"public"},"peerDependencies":{"canxjs":">=1.0.0"},"devDependencies":{"typescript":"^5.0.0","canxjs":"^1.6.1"},"_id":"@canxjs/citadel@1.0.0","bugs":{"url":"https://github.com/chandafa/canxjs/issues"},"homepage":"https://github.com/chandafa/canxjs#readme","_nodeVersion":"22.13.1","_npmVersion":"11.7.0","dist":{"integrity":"sha512-Aibd2nGd4Sb1WEPBv5Tzp2nP4eu4Q6j5G7ktHj5xPhXB2iVCpSZma4MbK/N8mE76nm4nyj9944aU8XZRZfKWbg==","shasum":"06033d7e8c8358ff7cafb3f258f0842c2626a86f","tarball":"https://registry.npmjs.org/@canxjs/citadel/-/citadel-1.0.0.tgz","fileCount":234,"unpackedSize":963145,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDsa4mN6WS+7RnxvKKwsjwZvR6FvvO48dCxRzi4UHxWEAIhAKsgF+jlyOJxTlYXCH7NhXs9znRh5iWi4j2MtQXy9VJz"}]},"_npmUser":{"name":"chandafa","email":"chankirana722@gmail.com"},"directories":{},"maintainers":[{"name":"chandafa","email":"chankirana722@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/citadel_1.0.0_1769930750069_0.5352649185474743"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-01T07:25:49.819Z","1.0.0":"2026-02-01T07:25:50.258Z","modified":"2026-02-01T07:25:50.653Z"},"maintainers":[{"name":"chandafa","email":"chankirana722@gmail.com"}],"description":"CanxJS Citadel - Secure API Token Authentication","homepage":"https://github.com/chandafa/canxjs#readme","keywords":["canxjs","auth","sanctum","api","token"],"repository":{"type":"git","url":"git+https://github.com/chandafa/canxjs.git","directory":"packages/citadel"},"author":{"name":"CanxJS Team"},"bugs":{"url":"https://github.com/chandafa/canxjs/issues"},"license":"MIT","readme":"# @canxjs/citadel\r\n\r\n<p align=\"center\">\r\n  <strong>Secure API Token Authentication for CanxJS</strong>\r\n</p>\r\n\r\n<p align=\"center\">\r\n  A featherweight authentication system for SPAs, mobile applications, and simple token-based APIs.\r\n  <br />\r\n  Inspired by Laravel Sanctum.\r\n</p>\r\n\r\n---\r\n\r\n## ✨ Features\r\n\r\n- 🔐 **Personal Access Tokens** - Issue API tokens with custom abilities/scopes\r\n- ⚡ **Lightweight** - Minimal overhead, maximum security\r\n- 🎯 **Fine-grained Permissions** - Control what each token can do\r\n- 🔄 **Token Revocation** - Easily revoke compromised tokens\r\n- 📦 **Zero Config** - Works out of the box with CanxJS\r\n\r\n---\r\n\r\n## 📦 Installation\r\n\r\n```bash\r\nnpm install @canxjs/citadel\r\n# or\r\nbun add @canxjs/citadel\r\n```\r\n\r\n---\r\n\r\n## 🚀 Quick Start\r\n\r\n### 1. Register the Service Provider\r\n\r\n```typescript\r\n// src/providers.ts\r\nimport { CitadelServiceProvider } from \"@canxjs/citadel\";\r\n\r\nexport const providers = [\r\n  // ... other providers\r\n  CitadelServiceProvider,\r\n];\r\n```\r\n\r\n### 2. Run the Install Command\r\n\r\nThis will publish the necessary migration files:\r\n\r\n```bash\r\nnode canx citadel:install\r\nnode canx migrate\r\n```\r\n\r\n### 3. Add Mixin to User Model\r\n\r\n```typescript\r\nimport { Model } from \"canxjs\";\r\nimport { HasApiTokens } from \"@canxjs/citadel\";\r\n\r\nclass User extends HasApiTokens(Model) {\r\n  static tableName = \"users\";\r\n\r\n  id!: number;\r\n  email!: string;\r\n  // ... other fields\r\n}\r\n```\r\n\r\n---\r\n\r\n## 📖 Usage\r\n\r\n### Issuing Tokens\r\n\r\n```typescript\r\nconst user = await User.find(1);\r\n\r\n// Create a token with all abilities\r\nconst { plainTextToken } = await user.createToken(\"my-app-token\");\r\n\r\n// Create a token with specific abilities\r\nconst { plainTextToken } = await user.createToken(\"limited-token\", [\r\n  \"read:posts\",\r\n  \"create:posts\",\r\n]);\r\n\r\n// Create a token with expiration\r\nconst expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days\r\nconst { plainTextToken } = await user.createToken(\r\n  \"temp-token\",\r\n  [\"*\"],\r\n  expiresAt,\r\n);\r\n\r\n// Return the plain text token to the client (only visible once!)\r\nreturn response.json({ token: plainTextToken });\r\n```\r\n\r\n### Checking Token Abilities\r\n\r\n```typescript\r\n// In your controller or middleware\r\nif (user.tokenCan(\"create:posts\")) {\r\n  // User's current token has this ability\r\n}\r\n\r\n// Check multiple abilities\r\nconst canManagePosts =\r\n  user.tokenCan(\"create:posts\") && user.tokenCan(\"delete:posts\");\r\n```\r\n\r\n### Protecting Routes\r\n\r\n```typescript\r\nimport { router } from \"canxjs\";\r\n\r\n// Protect with auth middleware\r\nrouter\r\n  .get(\"/api/user\", (req) => {\r\n    return req.user;\r\n  })\r\n  .middleware(\"auth\");\r\n\r\n// Check abilities in route\r\nrouter\r\n  .post(\"/api/posts\", (req) => {\r\n    if (!req.user.tokenCan(\"create:posts\")) {\r\n      return response.status(403).json({ error: \"Insufficient permissions\" });\r\n    }\r\n    // Create post...\r\n  })\r\n  .middleware(\"auth\");\r\n```\r\n\r\n---\r\n\r\n## 🔧 How It Works\r\n\r\n### Token Storage\r\n\r\nCitadel stores tokens in the `personal_access_tokens` table:\r\n\r\n| Column           | Type     | Description                     |\r\n| ---------------- | -------- | ------------------------------- |\r\n| `id`             | integer  | Primary key                     |\r\n| `tokenable_type` | string   | Model class name (e.g., \"User\") |\r\n| `tokenable_id`   | integer  | The user's ID                   |\r\n| `name`           | string   | Token name for identification   |\r\n| `token`          | string   | SHA-256 hash of the token       |\r\n| `abilities`      | json     | Array of allowed abilities      |\r\n| `last_used_at`   | datetime | Last usage timestamp            |\r\n| `expires_at`     | datetime | Optional expiration             |\r\n| `created_at`     | datetime | Creation timestamp              |\r\n\r\n### Token Format\r\n\r\nTokens are returned in the format: `{id}|{random_string}`\r\n\r\n- The `id` identifies which token record to look up\r\n- The `random_string` is hashed and compared against the stored hash\r\n- This prevents timing attacks and ensures tokens can't be guessed\r\n\r\n### Authentication Flow\r\n\r\n1. Client sends token in `Authorization: Bearer {token}` header\r\n2. Middleware extracts token and splits by `|`\r\n3. Looks up `PersonalAccessToken` by ID\r\n4. Hashes the random part and compares with stored hash\r\n5. If valid, attaches user and token to request\r\n\r\n---\r\n\r\n## 📚 API Reference\r\n\r\n### `HasApiTokens` Mixin\r\n\r\n| Method                                      | Description                             |\r\n| ------------------------------------------- | --------------------------------------- |\r\n| `createToken(name, abilities?, expiresAt?)` | Creates a new personal access token     |\r\n| `tokens()`                                  | Returns the user's tokens relationship  |\r\n| `tokenCan(ability)`                         | Checks if current token has the ability |\r\n\r\n### `PersonalAccessToken` Model\r\n\r\n| Method          | Description                           |\r\n| --------------- | ------------------------------------- |\r\n| `can(ability)`  | Check if token has specific ability   |\r\n| `cant(ability)` | Check if token lacks specific ability |\r\n\r\n---\r\n\r\n## 📄 License\r\n\r\nMIT © CanxJS Team\r\n","readmeFilename":"README.md","_rev":"1-f799c0b161071b8b2cd1794cc034a9e7"}