@kb-labs/gateway-auth 0.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # TODO
package/dist/index.js CHANGED
@@ -125,6 +125,8 @@ var AuthService = class {
125
125
  this.cache = cache;
126
126
  this.jwtConfig = jwtConfig;
127
127
  }
128
+ cache;
129
+ jwtConfig;
128
130
  // ── Register ──────────────────────────────────────────────────────────────
129
131
  async register(req) {
130
132
  const secret = generateClientSecret();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store.ts","../src/jwt.ts","../src/service.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Auth storage — ICache operations for credentials and tokens.\n *\n * Key schema:\n * auth:client:{clientId} → ClientRecord (permanent)\n * auth:refresh:{tokenHash} → { hostId } (TTL 30d)\n * auth:publickey:{hostId} → string (base64url X25519 public key)\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport type { ICache } from '@kb-labs/core-platform';\n\nconst REFRESH_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days\n\nexport interface ClientRecord {\n clientId: string;\n /** bcrypt-style: we store sha256 hash of the secret for simplicity (no bcrypt dep) */\n secretHash: string;\n hostId: string;\n namespaceId: string;\n tier: 'free' | 'pro' | 'enterprise';\n name: string;\n capabilities: string[];\n publicKey?: string;\n createdAt: number;\n}\n\nfunction hashSecret(secret: string): string {\n return createHash('sha256').update(secret).digest('hex');\n}\n\nfunction hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\nexport function generateClientId(): string {\n return `clt_${randomBytes(16).toString('hex')}`;\n}\n\nexport function generateClientSecret(): string {\n return `cs_${randomBytes(32).toString('base64url')}`;\n}\n\nexport function generateHostId(): string {\n return `host_${randomBytes(12).toString('hex')}`;\n}\n\n// ── Client CRUD ───────────────────────────────────────────────────────────────\n\nexport async function saveClient(cache: ICache, record: ClientRecord): Promise<void> {\n await cache.set(`auth:client:${record.clientId}`, record);\n // Reverse index: hostId → clientId for WS handler lookups\n await cache.set(`auth:hostindex:${record.hostId}`, record.clientId);\n}\n\nexport async function getClientByHostId(cache: ICache, hostId: string): Promise<ClientRecord | null> {\n const clientId = await cache.get<string>(`auth:hostindex:${hostId}`);\n if (!clientId) {return null;}\n return getClient(cache, clientId);\n}\n\nexport async function getClient(cache: ICache, clientId: string): Promise<ClientRecord | null> {\n return cache.get<ClientRecord>(`auth:client:${clientId}`);\n}\n\nexport async function verifyClientSecret(\n cache: ICache,\n clientId: string,\n secret: string,\n): Promise<ClientRecord | null> {\n const record = await getClient(cache, clientId);\n if (!record) {return null;}\n if (record.secretHash !== hashSecret(secret)) {return null;}\n return record;\n}\n\nexport function buildClientRecord(opts: {\n name: string;\n namespaceId: string;\n capabilities: string[];\n publicKey?: string;\n secret: string;\n}): ClientRecord {\n return {\n clientId: generateClientId(),\n secretHash: hashSecret(opts.secret),\n hostId: generateHostId(),\n namespaceId: opts.namespaceId,\n tier: 'free',\n name: opts.name,\n capabilities: opts.capabilities,\n publicKey: opts.publicKey,\n createdAt: Date.now(),\n };\n}\n\n// ── Refresh tokens ────────────────────────────────────────────────────────────\n\nexport async function saveRefreshToken(\n cache: ICache,\n token: string,\n hostId: string,\n namespaceId: string,\n): Promise<void> {\n await cache.set(`auth:refresh:${hashToken(token)}`, { hostId, namespaceId }, REFRESH_TTL_MS);\n}\n\nexport async function consumeRefreshToken(\n cache: ICache,\n token: string,\n): Promise<{ hostId: string; namespaceId: string } | null> {\n const key = `auth:refresh:${hashToken(token)}`;\n const entry = await cache.get<{ hostId: string; namespaceId: string }>(key);\n if (!entry) {return null;}\n // Rotate: delete old token (new one will be issued)\n await cache.delete(key);\n return entry;\n}\n\n// ── Public keys (E2E encryption) ──────────────────────────────────────────────\n\nexport async function savePublicKey(\n cache: ICache,\n hostId: string,\n publicKey: string,\n): Promise<void> {\n await cache.set(`auth:publickey:${hostId}`, publicKey);\n}\n\nexport async function getPublicKey(\n cache: ICache,\n hostId: string,\n): Promise<string | null> {\n return cache.get<string>(`auth:publickey:${hostId}`);\n}\n","/**\n * JWT sign / verify / refresh logic.\n * Pure functions — no side effects, no ICache dependency.\n */\n\nimport { SignJWT, jwtVerify, type JWTPayload } from 'jose';\nimport { randomUUID } from 'node:crypto';\nimport type { JwtPayload, TokenType } from '@kb-labs/gateway-contracts';\n\nconst ACCESS_TOKEN_TTL = 15 * 60; // 15 minutes in seconds\nconst REFRESH_TOKEN_TTL = 30 * 24 * 3600; // 30 days in seconds\n\nexport interface JwtConfig {\n /** Raw secret string — converted to TextEncoder internally */\n secret: string;\n}\n\nfunction getSecretKey(secret: string): Uint8Array {\n return new TextEncoder().encode(secret);\n}\n\nexport interface SignAccessTokenOptions {\n hostId: string;\n namespaceId: string;\n tier: 'free' | 'pro' | 'enterprise';\n type: TokenType;\n}\n\nexport async function signAccessToken(\n opts: SignAccessTokenOptions,\n config: JwtConfig,\n): Promise<{ token: string; expiresIn: number }> {\n const key = getSecretKey(config.secret);\n const now = Math.floor(Date.now() / 1000);\n\n const token = await new SignJWT({\n namespaceId: opts.namespaceId,\n tier: opts.tier,\n type: opts.type,\n })\n .setProtectedHeader({ alg: 'HS256' })\n .setSubject(opts.hostId)\n .setIssuedAt(now)\n .setExpirationTime(now + ACCESS_TOKEN_TTL)\n .sign(key);\n\n return { token, expiresIn: ACCESS_TOKEN_TTL };\n}\n\nexport async function signRefreshToken(\n hostId: string,\n config: JwtConfig,\n): Promise<string> {\n const key = getSecretKey(config.secret);\n const now = Math.floor(Date.now() / 1000);\n const jti = randomUUID();\n\n return new SignJWT({ type: 'refresh', jti })\n .setProtectedHeader({ alg: 'HS256' })\n .setSubject(hostId)\n .setIssuedAt(now)\n .setExpirationTime(now + REFRESH_TOKEN_TTL)\n .sign(key);\n}\n\nexport async function verifyAccessToken(\n token: string,\n config: JwtConfig,\n): Promise<JwtPayload | null> {\n try {\n const key = getSecretKey(config.secret);\n const { payload } = await jwtVerify(token, key);\n return payload as unknown as JwtPayload;\n } catch {\n return null;\n }\n}\n\nexport async function verifyRefreshToken(\n token: string,\n config: JwtConfig,\n): Promise<{ hostId: string } | null> {\n try {\n const key = getSecretKey(config.secret);\n const { payload } = await jwtVerify(token, key);\n if ((payload as JWTPayload & { type?: string }).type !== 'refresh') {\n return null;\n }\n if (!payload.sub) {return null;}\n return { hostId: payload.sub };\n } catch {\n return null;\n }\n}\n","/**\n * AuthService — orchestrates registration, token issuance, and refresh.\n * Depends on ICache (via store) and JWT config.\n */\n\nimport type { ICache } from '@kb-labs/core-platform';\nimport type {\n RegisterRequest,\n RegisterResponse,\n TokenResponse,\n AuthContext,\n} from '@kb-labs/gateway-contracts';\nimport {\n buildClientRecord,\n generateClientSecret,\n saveClient,\n verifyClientSecret,\n saveRefreshToken,\n consumeRefreshToken,\n savePublicKey,\n} from './store.js';\nimport {\n signAccessToken,\n signRefreshToken,\n verifyAccessToken,\n verifyRefreshToken,\n type JwtConfig,\n} from './jwt.js';\n\nexport class AuthService {\n constructor(\n private readonly cache: ICache,\n private readonly jwtConfig: JwtConfig,\n ) {}\n\n // ── Register ──────────────────────────────────────────────────────────────\n\n async register(req: RegisterRequest): Promise<RegisterResponse & { clientSecret: string }> {\n const secret = generateClientSecret();\n const record = buildClientRecord({\n name: req.name,\n namespaceId: req.namespaceId,\n capabilities: req.capabilities ?? [],\n publicKey: req.publicKey,\n secret,\n });\n\n await saveClient(this.cache, record);\n\n if (req.publicKey) {\n await savePublicKey(this.cache, record.hostId, req.publicKey);\n }\n\n return {\n clientId: record.clientId,\n clientSecret: secret, // returned ONCE, never stored in plaintext\n hostId: record.hostId,\n };\n }\n\n // ── Issue token pair ──────────────────────────────────────────────────────\n\n async issueTokens(clientId: string, clientSecret: string): Promise<TokenResponse | null> {\n const record = await verifyClientSecret(this.cache, clientId, clientSecret);\n if (!record) {return null;}\n\n const [{ token: accessToken, expiresIn }, refreshToken] = await Promise.all([\n signAccessToken(\n {\n hostId: record.hostId,\n namespaceId: record.namespaceId,\n tier: record.tier,\n type: 'machine',\n },\n this.jwtConfig,\n ),\n signRefreshToken(record.hostId, this.jwtConfig),\n ]);\n\n await saveRefreshToken(this.cache, refreshToken, record.hostId, record.namespaceId);\n\n return { accessToken, refreshToken, expiresIn, tokenType: 'Bearer' };\n }\n\n // ── Refresh token pair ────────────────────────────────────────────────────\n\n async refreshTokens(refreshToken: string): Promise<TokenResponse | null> {\n // Verify JWT signature first\n const jwtResult = await verifyRefreshToken(refreshToken, this.jwtConfig);\n if (!jwtResult) {return null;}\n\n // Consume from store (rotation — old token invalidated)\n const stored = await consumeRefreshToken(this.cache, refreshToken);\n if (!stored) {return null;}\n\n // Reload client record to get current tier/namespace\n // We need to scan by hostId — store clientId → hostId mapping for lookup\n // For now: resolve via a secondary key written at registration\n const hostId = stored.hostId;\n\n // Issue new pair\n const [{ token: accessToken, expiresIn }, newRefreshToken] = await Promise.all([\n signAccessToken(\n { hostId, namespaceId: stored.namespaceId, tier: 'free', type: 'machine' },\n this.jwtConfig,\n ),\n signRefreshToken(hostId, this.jwtConfig),\n ]);\n\n await saveRefreshToken(this.cache, newRefreshToken, hostId, stored.namespaceId);\n\n return { accessToken, refreshToken: newRefreshToken, expiresIn, tokenType: 'Bearer' };\n }\n\n // ── Verify access token → AuthContext ────────────────────────────────────\n\n async verify(token: string): Promise<AuthContext | null> {\n const payload = await verifyAccessToken(token, this.jwtConfig);\n if (!payload) {return null;}\n\n return {\n type: payload.type,\n userId: payload.sub,\n namespaceId: payload.namespaceId,\n tier: payload.tier,\n permissions: ['host:connect'],\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/store.ts","../src/jwt.ts","../src/service.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Auth storage — ICache operations for credentials and tokens.\n *\n * Key schema:\n * auth:client:{clientId} → ClientRecord (permanent)\n * auth:refresh:{tokenHash} → { hostId } (TTL 30d)\n * auth:publickey:{hostId} → string (base64url X25519 public key)\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport type { ICache } from '@kb-labs/core-platform';\n\nconst REFRESH_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days\n\nexport interface ClientRecord {\n clientId: string;\n /** bcrypt-style: we store sha256 hash of the secret for simplicity (no bcrypt dep) */\n secretHash: string;\n hostId: string;\n namespaceId: string;\n tier: 'free' | 'pro' | 'enterprise';\n name: string;\n capabilities: string[];\n publicKey?: string;\n createdAt: number;\n}\n\nfunction hashSecret(secret: string): string {\n return createHash('sha256').update(secret).digest('hex');\n}\n\nfunction hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\nexport function generateClientId(): string {\n return `clt_${randomBytes(16).toString('hex')}`;\n}\n\nexport function generateClientSecret(): string {\n return `cs_${randomBytes(32).toString('base64url')}`;\n}\n\nexport function generateHostId(): string {\n return `host_${randomBytes(12).toString('hex')}`;\n}\n\n// ── Client CRUD ───────────────────────────────────────────────────────────────\n\nexport async function saveClient(cache: ICache, record: ClientRecord): Promise<void> {\n await cache.set(`auth:client:${record.clientId}`, record);\n // Reverse index: hostId → clientId for WS handler lookups\n await cache.set(`auth:hostindex:${record.hostId}`, record.clientId);\n}\n\nexport async function getClientByHostId(cache: ICache, hostId: string): Promise<ClientRecord | null> {\n const clientId = await cache.get<string>(`auth:hostindex:${hostId}`);\n if (!clientId) {return null;}\n return getClient(cache, clientId);\n}\n\nexport async function getClient(cache: ICache, clientId: string): Promise<ClientRecord | null> {\n return cache.get<ClientRecord>(`auth:client:${clientId}`);\n}\n\nexport async function verifyClientSecret(\n cache: ICache,\n clientId: string,\n secret: string,\n): Promise<ClientRecord | null> {\n const record = await getClient(cache, clientId);\n if (!record) {return null;}\n if (record.secretHash !== hashSecret(secret)) {return null;}\n return record;\n}\n\nexport function buildClientRecord(opts: {\n name: string;\n namespaceId: string;\n capabilities: string[];\n publicKey?: string;\n secret: string;\n}): ClientRecord {\n return {\n clientId: generateClientId(),\n secretHash: hashSecret(opts.secret),\n hostId: generateHostId(),\n namespaceId: opts.namespaceId,\n tier: 'free',\n name: opts.name,\n capabilities: opts.capabilities,\n publicKey: opts.publicKey,\n createdAt: Date.now(),\n };\n}\n\n// ── Refresh tokens ────────────────────────────────────────────────────────────\n\nexport async function saveRefreshToken(\n cache: ICache,\n token: string,\n hostId: string,\n namespaceId: string,\n): Promise<void> {\n await cache.set(`auth:refresh:${hashToken(token)}`, { hostId, namespaceId }, REFRESH_TTL_MS);\n}\n\nexport async function consumeRefreshToken(\n cache: ICache,\n token: string,\n): Promise<{ hostId: string; namespaceId: string } | null> {\n const key = `auth:refresh:${hashToken(token)}`;\n const entry = await cache.get<{ hostId: string; namespaceId: string }>(key);\n if (!entry) {return null;}\n // Rotate: delete old token (new one will be issued)\n await cache.delete(key);\n return entry;\n}\n\n// ── Public keys (E2E encryption) ──────────────────────────────────────────────\n\nexport async function savePublicKey(\n cache: ICache,\n hostId: string,\n publicKey: string,\n): Promise<void> {\n await cache.set(`auth:publickey:${hostId}`, publicKey);\n}\n\nexport async function getPublicKey(\n cache: ICache,\n hostId: string,\n): Promise<string | null> {\n return cache.get<string>(`auth:publickey:${hostId}`);\n}\n","/**\n * JWT sign / verify / refresh logic.\n * Pure functions — no side effects, no ICache dependency.\n */\n\nimport { SignJWT, jwtVerify, type JWTPayload } from 'jose';\nimport { randomUUID } from 'node:crypto';\nimport type { JwtPayload, TokenType } from '@kb-labs/gateway-contracts';\n\nconst ACCESS_TOKEN_TTL = 15 * 60; // 15 minutes in seconds\nconst REFRESH_TOKEN_TTL = 30 * 24 * 3600; // 30 days in seconds\n\nexport interface JwtConfig {\n /** Raw secret string — converted to TextEncoder internally */\n secret: string;\n}\n\nfunction getSecretKey(secret: string): Uint8Array {\n return new TextEncoder().encode(secret);\n}\n\nexport interface SignAccessTokenOptions {\n hostId: string;\n namespaceId: string;\n tier: 'free' | 'pro' | 'enterprise';\n type: TokenType;\n}\n\nexport async function signAccessToken(\n opts: SignAccessTokenOptions,\n config: JwtConfig,\n): Promise<{ token: string; expiresIn: number }> {\n const key = getSecretKey(config.secret);\n const now = Math.floor(Date.now() / 1000);\n\n const token = await new SignJWT({\n namespaceId: opts.namespaceId,\n tier: opts.tier,\n type: opts.type,\n })\n .setProtectedHeader({ alg: 'HS256' })\n .setSubject(opts.hostId)\n .setIssuedAt(now)\n .setExpirationTime(now + ACCESS_TOKEN_TTL)\n .sign(key);\n\n return { token, expiresIn: ACCESS_TOKEN_TTL };\n}\n\nexport async function signRefreshToken(\n hostId: string,\n config: JwtConfig,\n): Promise<string> {\n const key = getSecretKey(config.secret);\n const now = Math.floor(Date.now() / 1000);\n const jti = randomUUID();\n\n return new SignJWT({ type: 'refresh', jti })\n .setProtectedHeader({ alg: 'HS256' })\n .setSubject(hostId)\n .setIssuedAt(now)\n .setExpirationTime(now + REFRESH_TOKEN_TTL)\n .sign(key);\n}\n\nexport async function verifyAccessToken(\n token: string,\n config: JwtConfig,\n): Promise<JwtPayload | null> {\n try {\n const key = getSecretKey(config.secret);\n const { payload } = await jwtVerify(token, key);\n return payload as unknown as JwtPayload;\n } catch {\n return null;\n }\n}\n\nexport async function verifyRefreshToken(\n token: string,\n config: JwtConfig,\n): Promise<{ hostId: string } | null> {\n try {\n const key = getSecretKey(config.secret);\n const { payload } = await jwtVerify(token, key);\n if ((payload as JWTPayload & { type?: string }).type !== 'refresh') {\n return null;\n }\n if (!payload.sub) {return null;}\n return { hostId: payload.sub };\n } catch {\n return null;\n }\n}\n","/**\n * AuthService — orchestrates registration, token issuance, and refresh.\n * Depends on ICache (via store) and JWT config.\n */\n\nimport type { ICache } from '@kb-labs/core-platform';\nimport type {\n RegisterRequest,\n RegisterResponse,\n TokenResponse,\n AuthContext,\n} from '@kb-labs/gateway-contracts';\nimport {\n buildClientRecord,\n generateClientSecret,\n saveClient,\n verifyClientSecret,\n saveRefreshToken,\n consumeRefreshToken,\n savePublicKey,\n} from './store.js';\nimport {\n signAccessToken,\n signRefreshToken,\n verifyAccessToken,\n verifyRefreshToken,\n type JwtConfig,\n} from './jwt.js';\n\nexport class AuthService {\n constructor(\n private readonly cache: ICache,\n private readonly jwtConfig: JwtConfig,\n ) {}\n\n // ── Register ──────────────────────────────────────────────────────────────\n\n async register(req: RegisterRequest): Promise<RegisterResponse & { clientSecret: string }> {\n const secret = generateClientSecret();\n const record = buildClientRecord({\n name: req.name,\n namespaceId: req.namespaceId,\n capabilities: req.capabilities ?? [],\n publicKey: req.publicKey,\n secret,\n });\n\n await saveClient(this.cache, record);\n\n if (req.publicKey) {\n await savePublicKey(this.cache, record.hostId, req.publicKey);\n }\n\n return {\n clientId: record.clientId,\n clientSecret: secret, // returned ONCE, never stored in plaintext\n hostId: record.hostId,\n };\n }\n\n // ── Issue token pair ──────────────────────────────────────────────────────\n\n async issueTokens(clientId: string, clientSecret: string): Promise<TokenResponse | null> {\n const record = await verifyClientSecret(this.cache, clientId, clientSecret);\n if (!record) {return null;}\n\n const [{ token: accessToken, expiresIn }, refreshToken] = await Promise.all([\n signAccessToken(\n {\n hostId: record.hostId,\n namespaceId: record.namespaceId,\n tier: record.tier,\n type: 'machine',\n },\n this.jwtConfig,\n ),\n signRefreshToken(record.hostId, this.jwtConfig),\n ]);\n\n await saveRefreshToken(this.cache, refreshToken, record.hostId, record.namespaceId);\n\n return { accessToken, refreshToken, expiresIn, tokenType: 'Bearer' };\n }\n\n // ── Refresh token pair ────────────────────────────────────────────────────\n\n async refreshTokens(refreshToken: string): Promise<TokenResponse | null> {\n // Verify JWT signature first\n const jwtResult = await verifyRefreshToken(refreshToken, this.jwtConfig);\n if (!jwtResult) {return null;}\n\n // Consume from store (rotation — old token invalidated)\n const stored = await consumeRefreshToken(this.cache, refreshToken);\n if (!stored) {return null;}\n\n // Reload client record to get current tier/namespace\n // We need to scan by hostId — store clientId → hostId mapping for lookup\n // For now: resolve via a secondary key written at registration\n const hostId = stored.hostId;\n\n // Issue new pair\n const [{ token: accessToken, expiresIn }, newRefreshToken] = await Promise.all([\n signAccessToken(\n { hostId, namespaceId: stored.namespaceId, tier: 'free', type: 'machine' },\n this.jwtConfig,\n ),\n signRefreshToken(hostId, this.jwtConfig),\n ]);\n\n await saveRefreshToken(this.cache, newRefreshToken, hostId, stored.namespaceId);\n\n return { accessToken, refreshToken: newRefreshToken, expiresIn, tokenType: 'Bearer' };\n }\n\n // ── Verify access token → AuthContext ────────────────────────────────────\n\n async verify(token: string): Promise<AuthContext | null> {\n const payload = await verifyAccessToken(token, this.jwtConfig);\n if (!payload) {return null;}\n\n return {\n type: payload.type,\n userId: payload.sub,\n namespaceId: payload.namespaceId,\n tier: payload.tier,\n permissions: ['host:connect'],\n };\n }\n}\n"]}
package/package.json CHANGED
@@ -1,9 +1,20 @@
1
1
  {
2
- "name": "@kb-labs/gateway-auth",
3
- "version": "0.4.0",
4
- "type": "module",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
2
+ "dependencies": {
3
+ "@kb-labs/core-platform": "^2.6.0",
4
+ "@kb-labs/gateway-contracts": "^2.6.0",
5
+ "jose": "^5.9.6"
6
+ },
7
+ "devDependencies": {
8
+ "@kb-labs/devkit": "workspace:*",
9
+ "@types/node": "^20",
10
+ "tsup": "^8.5.0",
11
+ "typescript": "^5",
12
+ "vitest": "^3.2.4"
13
+ },
14
+ "engines": {
15
+ "node": ">=20.0.0",
16
+ "pnpm": ">=9.0.0"
17
+ },
7
18
  "exports": {
8
19
  ".": {
9
20
  "import": "./dist/index.js",
@@ -13,26 +24,20 @@
13
24
  "files": [
14
25
  "dist"
15
26
  ],
16
- "sideEffects": false,
27
+ "main": "./dist/index.js",
28
+ "name": "@kb-labs/gateway-auth",
17
29
  "scripts": {
18
- "clean": "rimraf dist",
19
30
  "build": "tsup",
31
+ "clean": "rimraf dist",
20
32
  "dev": "tsup --watch",
21
- "type-check": "tsc --noEmit",
22
33
  "lint": "eslint .",
23
34
  "lint:fix": "eslint . --fix",
24
35
  "test": "vitest run -c ../../vitest.config.ts",
25
- "test:watch": "vitest -c ../../vitest.config.ts"
26
- },
27
- "dependencies": {
28
- "@kb-labs/core-platform": "^1.6.0",
29
- "@kb-labs/gateway-contracts": "^0.4.0",
30
- "jose": "^5.9.6"
36
+ "test:watch": "vitest -c ../../vitest.config.ts",
37
+ "type-check": "tsc --noEmit"
31
38
  },
32
- "devDependencies": {
33
- "@kb-labs/devkit": "link:../../../kb-labs-devkit",
34
- "@types/node": "^20",
35
- "tsup": "^8.5.0",
36
- "vitest": "^3.2.4"
37
- }
39
+ "sideEffects": false,
40
+ "type": "module",
41
+ "types": "./dist/index.d.ts",
42
+ "version": "2.6.0"
38
43
  }