{"_id":"@anchrd/gate","_rev":"8-bac37ad4c72f7d52c6adc70eaf350ed9","name":"@anchrd/gate","dist-tags":{"latest":"0.35.0"},"versions":{"0.28.0":{"name":"@anchrd/gate","version":"0.28.0","license":"UNLICENSED","_id":"@anchrd/gate@0.28.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"420d5abfabd838828e4aa1834bbbc6214941cb7e","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.28.0.tgz","fileCount":215,"integrity":"sha512-jYEECtD88A3TBgoh3aOG7APUgjwY3qMktUeP+ahvN8ajjyLICzIy34Hoh7iXPNrg+CNV5S8Ro3i7nMMJiEKAOA==","signatures":[{"sig":"MEQCIF2RnVocUgxXFMUiosMMyVlQdT9Y8bj6jRvFCYLbXS2IAiBsjRt0T/pXxdzdxpOnDbdLdph6ytPNFKZzLoxHC/Ra4A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2317303},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"anchrd","email":"jack.schmidt@outlook.de"},"_npmVersion":"11.6.0","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"24.8.0","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.28.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@tanstack/react-router":"^1.135.0","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.28.0_1788169492112_0.018907776032649037","host":"s3://npm-registry-packages-npm-production"}},"0.29.0":{"name":"@anchrd/gate","version":"0.29.0","license":"UNLICENSED","_id":"@anchrd/gate@0.29.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"53da48dffb7ecfe9a8f680d87c6853ffd77c7091","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.29.0.tgz","fileCount":215,"integrity":"sha512-6JXlxD8rJz3lAiprFWedmDd5Eql8dH4aGxH6uB2cufZsMIhSw8byccqUpstXByWMIwVIRrzYeYfB3u62IAGOww==","signatures":[{"sig":"MEQCIFlRce1N49gdQKDE4gFhPy1SKoD4JkbgRmicUPhDtZ+MAiAJCcmSTiMKxTuHeHlJfuP+uIqE7GEGSeQBbyeLim3xvw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2305420},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"61b65a149b096ae31c35f549e341d272a8cfa990","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.29.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@tanstack/react-router":"^1.135.0","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.29.0_1788193046009_0.6260814981293916","host":"s3://npm-registry-packages-npm-production"}},"0.30.0":{"name":"@anchrd/gate","version":"0.30.0","license":"UNLICENSED","_id":"@anchrd/gate@0.30.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"a557b0aba470c140f6aeeacaa6a51593d8261531","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.30.0.tgz","fileCount":227,"integrity":"sha512-ht/pdA8TuYI8z0Mh2dhq3JQloI7aNAKubsxDSEe038GWhw6v8V3B4Y5GUjiRjE0Iyq3NtkDMY/5IsCO+0zknHw==","signatures":[{"sig":"MEQCIAYHCiCPRAL47OZBIqiLiYtyTmpcmauhRgleFCzum9krAiBPX3rUZQFwUvAEnJXsFsjIuCehLD1oGNCoGVnmBcf8SQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2609694},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"5e6c58ae43d12af0fa2fe527e0907733126efd75","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.30.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.30.0_1788298664134_0.4970484116796219","host":"s3://npm-registry-packages-npm-production"}},"0.31.0":{"name":"@anchrd/gate","version":"0.31.0","license":"UNLICENSED","_id":"@anchrd/gate@0.31.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"9fad0ec696bcd83dcd56adf34ab1b14229ed67bf","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.31.0.tgz","fileCount":237,"integrity":"sha512-89/U+esUgvi/A8TBkjo/k3cELrpBXAWDAOk4BsbICmeyFOLmH2I9M+DSqUikpkg42veSyivq5DnXwNyZGrd6mg==","signatures":[{"sig":"MEUCIQDwYG8K7RyaoPPfo4EdhmYF4T5nHf9pdDiatoCqyMHjuwIgBr52ijURIllWAUvDbij2UR/ECklRYS7E3Xupt0FCVBQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2850379},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"b4e8d6da426bd07b15772c0135a22acfb605c621","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.31.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.31.0_1788377670020_0.8115168920017846","host":"s3://npm-registry-packages-npm-production"}},"0.32.0":{"name":"@anchrd/gate","version":"0.32.0","license":"UNLICENSED","_id":"@anchrd/gate@0.32.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"dae46fe72488c8f9974bd4559f247101d04cd42c","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.32.0.tgz","fileCount":237,"integrity":"sha512-bpxKw7rb/FossexUBsxzRR1iUVWEv3e9V1o76Uhs5sRb+RfWJcvr9KJRirmX2UGgZMlkCvwTJv1xVIj6KIl9XA==","signatures":[{"sig":"MEUCIHi8lGd+Uy2bxnEaa8UuFCSUzyU2RIcw9akZPlKgliGIAiEAxFjteF9MaBc/XUYFaqjBg0VGC41/xvKxz99fxdtN0Es=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2850379},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"9d9d1ab589d4115257a3d041d10b1a29075d7e43","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.32.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.32.0_1788388290954_0.9418698919041459","host":"s3://npm-registry-packages-npm-production"}},"0.33.0":{"name":"@anchrd/gate","version":"0.33.0","license":"UNLICENSED","_id":"@anchrd/gate@0.33.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"62256bb7e908214ecc5e36760cd6f0745564b022","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.33.0.tgz","fileCount":237,"integrity":"sha512-TuqGQ4ricHVFZds8Kja4Nj7kuRH2O6qaNmH0MM7SjJyK/GiU2mNN0nVA6rmW3ag+n/rmfpC79HPoJEtU/T6ekA==","signatures":[{"sig":"MEYCIQCa9byat5KCi26R8JXFyWSyarFxHPzeRDQj5YkmkGV4yQIhAIZUTESkD2BtGM7lGm76uDBkGvyUT1Oq6BLnqs8VXn8/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2850379},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"38423825d81b0a2ce3ef676b114d373c6130d47c","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.33.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.33.0_1789581545956_0.41451794441006706","host":"s3://npm-registry-packages-npm-production"}},"0.34.0":{"name":"@anchrd/gate","version":"0.34.0","license":"UNLICENSED","_id":"@anchrd/gate@0.34.0","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"58cee8d7c9a0247fb716baa96f257abc3e4a2e05","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.34.0.tgz","fileCount":237,"integrity":"sha512-chsW2ESBr3tG78TeZoen0VaNd/kpBAex50m2BxPm2qSNjfEDKd5rj5Yg/jqa+pNOmxDNPZYVaqRfSq5s7n+KPA==","signatures":[{"sig":"MEQCICsopX8bJ+l9bCDfdQQC8BZYQUwG/mnLslNbylPRBnlgAiBw9xSD8X6bAU7Y+aIM6z2IFItQaTv21YFOf6IAagsgDw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQCd2I2c9sASlXeCulieEreJe4KW9aVgAchFOa3BH6CHvAIhAMk3Is/GqPnl/OTqKCPXe7YbiYbm9hront/2lQH5byn/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2847860},"type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"fe5ed89ad719103eca20474bd675f9a1d252b684","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.34.0","@tailwindcss/vite":"^4.3.3","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"tmp":"tmp/gate_0.34.0_1789671392369_0.42110243045981943","host":"s3://npm-registry-packages-npm-production"}},"0.35.0":{"_id":"@anchrd/gate@0.35.0","bin":{"gate":"bin/gate.mjs"},"dist":{"shasum":"b48337109b310ba22d25bac2f1a1868f39b2e068","tarball":"https://registry.npmjs.org/@anchrd/gate/-/gate-0.35.0.tgz","fileCount":241,"integrity":"sha512-3riIKQOenZ9xuYuFwsKEGsGghFv57mWWIckWcV7kTVu5+Wy3i7DMuINabr55s9wp1d+fQpnUR1xQLZFA7lCI1Q==","signatures":[{"sig":"MEUCIA85f5PmjWZ9e1RyoeC7KqJpTJwZ/1xinCqU1NkWJr8/AiEAvrPvgD7VaKPvwQQLlewcurKSSBQnFXqN9yfnY1nL9R0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDW0Bl/sRxUhm6QG+eJFh7r+45KMrHYgcNo0H54Vp/wrwIgEBJ+d5uf4rVjgZFK25p4y3tbwHvOMEBzvYtvLTFzcmM="}],"unpackedSize":2874770},"name":"@anchrd/gate","type":"module","exports":{".":"./src/gate/gate.ts","./cloudflare":"./src/adapters/cloudflare/cloudflare.ts"},"gitHead":"830d9a04ea4c9a6ba59c8e007b847b638c6062fd","license":"UNLICENSED","scripts":{"dev":"vite ui","lint":"biome check . && bun run lint:tokens","test":"vitest run","build":"bun run build:api && bun run build:ui","prepack":"bun run build:api && node ../../scripts/pack/to-dist-manifest.mjs","build:ui":"vite build ui","postpack":"node ../../scripts/pack/restore-manifest.mjs","build:api":"tsc -p tsconfig.build.json --emitDeclarationOnly && node ../../scripts/pack/fix-dts.mjs && esbuild src/gate/gate.ts src/adapters/cloudflare/cloudflare.ts --bundle --minify --format=esm --packages=external --outdir=dist --outbase=src","typecheck":"tsc --noEmit && tsc --noEmit -p ui/tsconfig.json","db:generate":"drizzle-kit generate","lint:tokens":"! grep -rnE '#[0-9a-fA-F]{3,6}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(white|black)|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}|(bg|text|border|ring|shadow|fill|stroke|outline|decoration|accent|caret|divide|placeholder|from|via|to)-[[](rgb|hsl|oklch|oklab|lab|lch|color)[(]' ui/src","typecheck:core":"tsc --noEmit -p tsconfig.portable.json","db:auth:generate":"better-auth generate --config better-auth.config.ts --output src/adapters/db/auth.schema.ts --yes && biome check --write src/adapters/db/auth.schema.ts","db:migrate:local":"wrangler d1 migrations apply gate --local"},"version":"0.35.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb07977e-7aed-457b-9e46-a73bfd798d1b"}},"_npmVersion":"12.0.2","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","directories":{},"maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3","clsx":"^2.1.1","cmdk":"^1.1.1","hono":"^4.12.30","jose":"^6.1.0","ulid":"^3.0.2","vite":"^8.1.5","react":"^19.2.0","radix-ui":"^1.6.4","react-dom":"^19.2.0","better-auth":"1.6.23","drizzle-orm":"^0.45.2","tailwindcss":"^4.3.3","lucide-react":"^1.25.0","@sentry/react":"10.75.0","tailwind-merge":"^3.6.0","react-hook-form":"^7.82.0","@anchrd/gate-sdk":"^0.35.0","@tailwindcss/vite":"^4.3.3","@sentry/cloudflare":"10.75.0","@hookform/resolvers":"^5.4.0","@better-auth/api-key":"1.6.23","@better-auth/passkey":"1.6.23","@vitejs/plugin-react":"^6.0.4","@tanstack/react-query":"^5.101.4","@tanstack/react-table":"^8","@simplewebauthn/server":"^13.2.3","@tanstack/react-router":"^1.135.0","@simplewebauthn/browser":"^13.2.2","class-variance-authority":"^0.7.1","@cloudflare/workers-types":"^5.20260718.1","@better-auth/oauth-provider":"1.6.23"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","vitest":"^4.1.10","wrangler":"^4.112.0","@types/node":"^24.13.3","drizzle-kit":"^0.31.10","@types/react":"^19.2.17","@better-auth/cli":"1.4.22","@types/react-dom":"^19.2.3","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@cloudflare/vitest-pool-workers":"^0.18.6"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/gate_0.35.0_1789752054914_0.8292056820738041"}}},"time":{"created":"2026-08-31T09:44:51.767Z","modified":"2026-09-18T17:20:55.193Z","0.28.0":"2026-08-31T09:44:52.248Z","0.29.0":"2026-08-31T16:17:26.187Z","0.30.0":"2026-09-01T21:37:44.331Z","0.31.0":"2026-09-02T19:34:30.153Z","0.32.0":"2026-09-02T22:31:31.098Z","0.33.0":"2026-09-16T17:59:06.118Z","0.34.0":"2026-09-17T18:56:32.614Z","0.35.0":"2026-09-18T17:20:55.035Z"},"license":"UNLICENSED","description":"**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic, [D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer, steerable from the outside over **REST** and **MCP**.","maintainers":[{"name":"anchrd","email":"jack.schmidt@outlook.de"}],"readme":"# @anchrd/gate\n\n**gate** — login and permissions as a finished product. A Cloudflare Worker holds the logic,\n[D1](https://developers.cloudflare.com/d1/) holds everything as data. Deployable per customer,\nsteerable from the outside over **REST** and **MCP**.\n\nBuilt on [Better Auth](https://better-auth.com), [Hono](https://hono.dev),\n[Drizzle](https://orm.drizzle.team), and Zod.\n\n## Install\n\n```bash\nnpm i @anchrd/gate\n```\n\n## What it is\n\nOne question matters: **`can(interface, function)`** — may this user perform this action?\ngate holds users, roles with their permissions, registered interfaces, and an append-only audit log. A customer edge\nasks `/api/v1/authorization` once per request and gets back `{ identity, rules }` — you never write code\ninside gate; everything is data.\n\nThe most convenient way to steer and consume gate is [`@anchrd/gate-sdk`](https://www.npmjs.com/package/@anchrd/gate-sdk) (client + `gate` CLI).\n\n**Where this package stops.** It needs a D1 binding and a rate-limit binding, and it needs\n`BETTER_AUTH_URL` and `BETTER_AUTH_SECRET`; without them it does not come up in a usable state. It\nis not a library you call in-process — every consumer talks HTTP. It does not run your service's\nauthorization for you: it answers *what may this identity do*, and the decision is made in your own\ncode by `can()`. It carries the admin UI but does not serve it unless you ask\nfor it (the `assets` binding below is optional), and it stores no business data — only principals,\nroles, permissions, registered interfaces and the audit log.\n\n## Setup (Cloudflare Worker)\n\nThe core is a factory (`createGate(deps)`); a **thin worker shell** injects the bindings.\nThe ready-made `./cloudflare` export is exactly that shell — your worker entry re-exports it:\n\n```ts\n// api.ts — your wrangler `main`\nimport worker from \"@anchrd/gate/cloudflare\";\nexport default worker;\n```\n\n```jsonc\n// wrangler.jsonc\n{\n  \"name\": \"gate\",\n  \"main\": \"api.ts\",\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"d1_databases\": [{ \"binding\": \"DB\", \"database_name\": \"gate\" }],\n  \"ratelimits\": [{ \"name\": \"RATE_LIMIT\", \"simple\": { \"limit\": 100, \"period\": 60 } }],\n  \"vars\": {\n    \"BETTER_AUTH_URL\": \"https://gate.your-customer.dev\",\n    \"GATE_ADMIN_EMAIL\": \"\",\n    \"GATE_OAUTH_AUDIENCES\": \"https://operations-dwh.example.com/mcp,https://operations-intel.example.com/mcp\"\n  }\n}\n```\n\nOne required secret (not in `vars`):\n\n```bash\nwrangler secret put BETTER_AUTH_SECRET     # ≥ 32 bytes of randomness\n```\n\nApply the migrations and deploy. The first human who signs up becomes administrator and can sign in\nwithout email verification; every later account must verify its address normally. Between deployment\nand that first sign-up, the publicly reachable installation can be claimed. Sign up first, then\nannounce or expose the installation to its intended audience.\n\n`GATE_ADMIN_EMAIL` is an optional emergency exit, not bootstrap configuration. Leave it empty during\nnormal operation. If all administrator roles are lost, set it to the email address of an existing\naccount; the shared admin guard then admits that account so roles can be repaired. Remove the value\nagain after recovery.\n\n`GATE_OAUTH_AUDIENCES` is an optional, comma-separated allowlist for MCP resource servers hosted\noutside gate. Add their exact public `resource` URLs (including `/mcp` when applicable). Gate then\naccepts RFC 8707 `resource` parameters for those URLs and issues access tokens with the requested\naudience; every URL not on the allowlist remains rejected. Without this value, only\n`BETTER_AUTH_URL` is a valid audience.\n\nWhen registering such a service, set its `oauthResource` to the same exact URL. The service key then\naccepts only JWT access tokens minted for that resource; a token for another configured resource is\nrejected.\n\nHosting elsewhere means swapping the `./cloudflare` shell plus the `db`/`mailer` adapters; the core beneath knows no `env`.\n\n## Social sign-in (optional: Google, Microsoft)\n\nUsers can sign in with Google or Microsoft (OIDC). A provider is active **exactly when all its\nvalues are set** — there is no second switch. Set none and gate behaves as before (the login page\nshows only email/password). Credentials are worker configuration, never `gate.json`: that file lives\nin your git and is baked into the UI at build time; a client secret has no place there and the\nworker could not read it anyway.\n\n```bash\nwrangler secret put GOOGLE_CLIENT_ID\nwrangler secret put GOOGLE_CLIENT_SECRET\nwrangler secret put MICROSOFT_CLIENT_ID\nwrangler secret put MICROSOFT_CLIENT_SECRET\n```\n\nRegister these **redirect URIs** at the provider (they are `<BETTER_AUTH_URL>/callback/<provider>`):\n\n| Provider | Where | Redirect URI |\n|---|---|---|\n| Google | [Google Cloud Console](https://console.cloud.google.com/apis/credentials) → OAuth client → *Authorized redirect URIs* | `https://gate.your-customer.dev/callback/google` |\n| Microsoft | [Entra ID](https://entra.microsoft.com) → App registration → *Authentication* → *Redirect URIs* (Web) | `https://gate.your-customer.dev/callback/microsoft` |\n\nSwap `gate.your-customer.dev` for your `BETTER_AUTH_URL`. The public `GET /api/v1/auth-options`\nreturns only each active provider's **name and sign-in path** (no client ID or secret), so the UI\nknows which button to show. The first human account becomes administrator regardless of whether it\nis created through OIDC or email/password. Every later OIDC account holds **no role** by default,\njust like every later email sign-up. Google can supply a verified email; Microsoft is forced to\n`emailVerified: false`, so gate's own verification mail must establish ownership before an\ninvitation can be accepted.\n\n> **Cloudflare Access as a way IN has been removed** (`#446`). `CLOUDFLARE_ACCESS_CLIENT_ID`,\n> `CLOUDFLARE_ACCESS_CLIENT_SECRET` and `CLOUDFLARE_ACCESS_TEAM_NAME` are no longer read; an\n> installation that still sets them is not broken, they are simply ignored. Existing `account` rows\n> for the provider are **kept**, and the account screen still lists and unlinks the leftover way.\n>\n> ⚠️ **Order matters, and there is no rescue afterwards.** An account whose only way in was Access\n> and that carries no password is locked out once the provider is gone: `sendResetPassword`\n> deliberately sends no reset link to an account without a `credential` row, only a hint pointing at\n> `/account`, and `/account` needs a session. Such an account must set a password through\n> `POST /set-password` **while** the provider is still configured.\n>\n> ⚠️ This does **not** touch the opposite direction: gate as the **identity provider for** Access.\n> `/oauth2/authorize`, the ES256 key set at `/jwks` and the `roles` claim in the ID token are\n> unchanged, and an Access application that uses gate as its OIDC login keeps working.\n\n## Second factor (optional, per person)\n\nAnyone with an account can switch on a second factor for themselves under **Account**: a **passkey**\n(WebAuthn, bound to the device) plus a set of **backup codes**. Nothing is switched on for anyone by\ngate, and no role or setting can require it.\n\n- **The passkey answers the challenge.** Signing in stays password-first: the password is checked,\n  and gate then asks for the passkey before it hands out a session. The passkey is the *second*\n  factor, not a replacement for the first one.\n- **The backup codes are the way back** when the device is gone: sign in with the password as usual,\n  then answer the same challenge with one code instead. Each code works once.\n- **They are shown once, at setup.** gate stores them hashed and has no endpoint that reads them\n  back; `POST /two-factor/generate-backup-codes` replaces the whole set with a new one.\n- **There is no code by email, and no TOTP app.** As long as the mailbox can reset the password, a\n  code sent to that same mailbox is the same factor twice, not a second one.\n\nTwo refusals exist so that nobody can lock themselves out, and both answer `409`:\n\n| You try to | gate answers |\n|---|---|\n| switch the factor on without a passkey | refused — the backup codes are the rescue *for* a passkey, not a factor of their own |\n| remove your last passkey while the factor is on | refused — switch the factor off first, then remove the passkey |\n\n> **On the API surface, `POST /passkey/verify-authentication` does create a session on its own**, the\n> way the Better Auth plugin ships it. The interface gate delivers does **not** offer that as a way\n> in: there is no \"sign in with a passkey\" button before the password, and the passkey is only ever\n> offered as the second step. Anything built on top of the API should say which of the two it means.\n\n> ⚠️ **A second factor protects the sign-in in a BROWSER. It does not protect the machine door.**\n> The challenge hangs on `/sign-in/email`, `/sign-in/username` and `/sign-in/phone-number` and on\n> nothing else — a request carrying an `x-api-key`, a bearer token or an OAuth access token is never\n> asked for a second factor, and its behaviour is unchanged by this feature.\n>\n> That is deliberate rather than an omission: a **machine principal cannot carry a second factor**,\n> because there is nobody present to answer the challenge. Any future rule that makes a second factor\n> mandatory has to exempt machine principals explicitly, or it locks out the very agents the\n> installation runs on.\n\n## Admin UI (optional, same-origin)\n\nThe admin SPA **travels inside this package**, under `ui/`, and is served as **Workers Static Assets\non this same worker** — one deploy per customer, no separate UI worker. Same-origin means the Better\nAuth session cookie just works: no CORS, no `SameSite` pain. Add an `assets` binding pointing at the\nbuilt UI:\n\n```jsonc\n// wrangler.jsonc — add alongside the config above\n{\n  \"assets\": {\n    \"directory\": \".gate/ui\",  // where `gate build` puts the built UI (see below)\n    \"binding\": \"ASSETS\"       // exposes env.ASSETS to the shell for the SPA fallback\n  }\n}\n```\n\nBuild the UI, then deploy the worker — `wrangler dev`/`deploy` serves UI **and** API from one\nprocess:\n\n```bash\nnpm i @anchrd/gate\nnpx gate build       # -> .gate/ui (reads your gate.json)\nwrangler deploy\n```\n\nThe UI ships as **Vite/React source**, not as a prebuilt bundle: `gate build` applies your\n`gate.json` — theme, logo, favicon, language catalogs — and builds it. A prebuilt `dist` could not\ndo that. The output belongs to **you** and is written outside `node_modules`, which the next\n`npm ci` would wipe:\n\n| the UI is found in | output |\n|---|---|\n| `node_modules/@anchrd/gate/ui` (installed) | `.gate/ui` in your project root |\n| `packages/gate/ui` (this workspace) | `packages/gate/ui/dist` |\n\nYou never `import` from the UI in your own code — building it is the whole interface. The same\ncommand mirrors this package's D1 migrations to `.gate/migrations`, so your `wrangler.jsonc` needs\nno path into a package's insides.\n\nRouting is automatic and needs no `main` changes: Cloudflare's asset router serves existing files\n(`/`, `/assets/*`) before the worker; the API routes (`/api/*`, `/oauth2/*`, `/.well-known/*`,\n`/mcp`, `/consent`, …) keep priority; and a browser navigation to a client route (`/grants`,\n`/roles`, …) falls back to `index.html` so deep-links and reloads work. The customer picks the\ndomain (e.g. `admin.gate.your-customer.dev`) in their own wrangler config. **Omit the `assets`\nbinding and gate is a pure API** — `env.ASSETS` is absent and the fallback is skipped.\n\n**Why the UI travels in here.** Until 0.26.0 it was a package of its own (`@anchrd/gate-ui`),\nversioned separately from the API. That bought a leaner tarball for a consumer who runs gate\nheadless — and there has never been one. What it cost was real: an installation had to pin two\nversion numbers for one product, and the two drifted apart. Since 0.28.0 there is one number.\n⚠️ **The price is named, not hidden:** whoever installs this package pulls the UI's build\ndependencies (`vite`, `react`, `tailwindcss`) even if they never run `gate build`. The repo's\nreference consumer ([`workers/api`](../../workers/api)) points its `assets.directory` straight at\nthe workspace build (`../../packages/gate/ui/dist`).\n\n## Surface\n\n| Path | |\n|---|---|\n| `POST /api/v1/authorization` | Bearer → `{ identity, rules }`, scoped to the calling service (service key; stamps \"last seen\"). One field is deliberately **not** scoped: `mcpConnectAnywhere` says whether this person holds `mcp.connect` at any service at all — a bit, never the list — because the MCP portal is one door for every server. |\n| `GET /api/v1/schema` | The calling service's own interfaces, for codegen (`gate init`). |\n| `PUT/DELETE /api/v1/service/interfaces/:handle` | Idempotent self-management of the calling app service's own interfaces. The service key supplies the scope; no service id is accepted. |\n| `/api/v1/services` · `/roles` · `/users` | Administration (admin). A service owns its interfaces (`/services/:id/interfaces`) and carries the one service key. A role owns its permissions: `/roles/:role/permissions` reads them as a tree (service → interface → function) and sets/unsets a single `(role, interfaceId, function)`. |\n| `/api/v1/users/:id/disable` · `/enable` | Lock a human account and release it again. Sessions are deleted, sign-in is refused, an already issued token stops resolving at the next protected access — identity and roles are kept. Nobody locks themselves, and nobody locks the last operational human admin. |\n| `/api/v1/applications` | Machine principals with scoped API keys (CI/CLI), including `/:id/disable` and `/:id/enable`. |\n| `/api/v1/oauth-clients` | The clients that registered themselves at the open `POST /oauth2/register` — list them with their registration date, `DELETE` one together with its tokens and consents. There is no create path here: the open endpoint is the only way in. |\n| `/mcp` | The same administration actions as MCP tools. |\n| `/auth/*` · `/oauth2/*` · `/.well-known/*` | Better Auth: login, OAuth provider, DCR, PKCE, discovery. |\n| `/health` | Whether the gate itself is alive. |\n\n**Two kinds of auth:** `authorization`, `schema`, and interface self-management accept the\n**service key**; `/api/v1/auth-options` is public. Everything else under `/api/v1` requires a\n**user bearer with admin rights** (or an application key holding the `admin` role).\n\n**Every service-key door is scoped to the calling service.** The key identifies a service, and gate\nanswers only about that service's own interfaces: `/authorization` intersects the user's permissions with\nthem (a data warehouse backend never learns what the same user may do on the dashboard MCP), and\n`/schema` generates types for those interfaces alone. This is also what keeps a handle unambiguous —\ntwo services may carry the same handle, but one response only ever describes one service.\nThe single exception is `POST /api/v1/users/invite/accept`: an invitation is accepted by the *invitee*,\nwho is not an admin. It still requires an authenticated session, and the role it grants comes from the\nstored invitation — never from the request body. Accepting only works on an account whose email is\n**verified**, which is what stops a stranger from pre-registering an address about to be invited.\n\n**Sign-up stays open, sign-in requires a verified email.** A newly created user holds no role and\ntherefore no access; `POST /sign-up/email` no longer returns a session, and the first sign-in attempt\nsends the verification mail through the injected mailer port. Sign-in, sign-up and invite are rate\nlimited through the `RATE_LIMIT` binding.\n\n## Related\n\n- [`@anchrd/gate-sdk`](https://www.npmjs.com/package/@anchrd/gate-sdk) — the client, the shared wire\n  formats and the `gate` CLI. That is the package a **consumer** of a gate installs; this one is\n  what an **operator** installs, and it depends on the SDK.\n","readmeFilename":"README.md"}