nestjs-slightly-better-auth 1.0.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/LICENSE +7 -0
- package/README.md +57 -0
- package/dist/admin.cjs +88 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +14 -0
- package/dist/admin.d.cts.map +1 -0
- package/dist/admin.d.mts +14 -0
- package/dist/admin.d.mts.map +1 -0
- package/dist/admin.mjs +85 -0
- package/dist/admin.mjs.map +1 -0
- package/dist/api-key.cjs +163 -0
- package/dist/api-key.cjs.map +1 -0
- package/dist/api-key.d.cts +42 -0
- package/dist/api-key.d.cts.map +1 -0
- package/dist/api-key.d.mts +42 -0
- package/dist/api-key.d.mts.map +1 -0
- package/dist/api-key.mjs +159 -0
- package/dist/api-key.mjs.map +1 -0
- package/dist/auth-contracts-BBA1C1gj.d.cts +1635 -0
- package/dist/auth-contracts-BBA1C1gj.d.cts.map +1 -0
- package/dist/auth-contracts-tqsfBZ8B.d.mts +1635 -0
- package/dist/auth-contracts-tqsfBZ8B.d.mts.map +1 -0
- package/dist/auth-decorators-CwW5JY4y.cjs +1274 -0
- package/dist/auth-decorators-CwW5JY4y.cjs.map +1 -0
- package/dist/auth-decorators-DfAJbHar.mjs +849 -0
- package/dist/auth-decorators-DfAJbHar.mjs.map +1 -0
- package/dist/auth-errors-CQjfgjaO.mjs +283 -0
- package/dist/auth-errors-CQjfgjaO.mjs.map +1 -0
- package/dist/auth-errors-CsERBNWI.cjs +348 -0
- package/dist/auth-errors-CsERBNWI.cjs.map +1 -0
- package/dist/express.cjs +247 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +37 -0
- package/dist/express.d.cts.map +1 -0
- package/dist/express.d.mts +37 -0
- package/dist/express.d.mts.map +1 -0
- package/dist/express.mjs +245 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.cjs +215 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +28 -0
- package/dist/fastify.d.cts.map +1 -0
- package/dist/fastify.d.mts +28 -0
- package/dist/fastify.d.mts.map +1 -0
- package/dist/fastify.mjs +213 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/index.cjs +2634 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2556 -0
- package/dist/index.mjs.map +1 -0
- package/dist/organization.cjs +191 -0
- package/dist/organization.cjs.map +1 -0
- package/dist/organization.d.cts +40 -0
- package/dist/organization.d.cts.map +1 -0
- package/dist/organization.d.mts +40 -0
- package/dist/organization.d.mts.map +1 -0
- package/dist/organization.mjs +177 -0
- package/dist/organization.mjs.map +1 -0
- package/dist/platform.cjs +393 -0
- package/dist/platform.cjs.map +1 -0
- package/dist/platform.d.cts +43 -0
- package/dist/platform.d.cts.map +1 -0
- package/dist/platform.d.mts +43 -0
- package/dist/platform.d.mts.map +1 -0
- package/dist/platform.mjs +385 -0
- package/dist/platform.mjs.map +1 -0
- package/dist/plugin.cjs +312 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +26 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.mts +26 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +310 -0
- package/dist/plugin.mjs.map +1 -0
- package/package.json +186 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 thilllon
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# nestjs-slightly-better-auth
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/nestjs-slightly-better-auth)
|
|
4
|
+
[](https://www.npmjs.com/package/nestjs-slightly-better-auth)
|
|
5
|
+
[](https://github.com/thilllon/nestjs-kit/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
A NestJS integration for [Better Auth](https://www.better-auth.com), built on an independently reviewed specification.
|
|
8
|
+
|
|
9
|
+
> **Scope of 1.0.0.** This release covers HTTP: the Nest authentication kernel, the Better Auth construction plugin, the Express and Fastify platforms, and the admin, organization and API-key authorization units. GraphQL, WebSocket and RPC transports and the conformance testing kit are not included; they arrive in later minor versions behind their own entry points.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add nestjs-slightly-better-auth better-auth
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Peer dependencies: `@nestjs/common` and `@nestjs/core` 12, `better-auth` 1.7.5 or newer, `reflect-metadata` and `rxjs`. Node.js 24.11 or newer.
|
|
18
|
+
|
|
19
|
+
## What this release covers
|
|
20
|
+
|
|
21
|
+
The library separates a Better Auth construction plugin, a NestJS integration kernel and optional transports. The `./plugin` entry installs the hook and cookie bridge before Better Auth creates its pipeline. The `./platform` entry provides Node/Web request and response helpers. Neither entry requires Express, Fastify, GraphQL or WebSocket packages.
|
|
22
|
+
|
|
23
|
+
The root entry provides synchronous and asynchronous module registration, default and named instances, service readers, guards, scoped execution and compositional authorization. Tests exercise actual Nest dependency injection and Better Auth sessions, including isolation between instances, caller-origin enforcement and application shutdown. Built ESM and CommonJS consumers verify the same registration and type contracts.
|
|
24
|
+
|
|
25
|
+
The `./express` and `./fastify` entries connect native Nest applications to Better Auth. Their end-to-end tests cover raw bodies, size limits, cookies, CORS, request cancellation, exception filters and real authentication. Fastify also supports HTTP/2. The platform entries have no runtime imports of Express or Fastify; install the Nest platform adapter used by your application.
|
|
26
|
+
|
|
27
|
+
Express preserves native controller parsing for unconditional routes that overlap the auth mount. Body-capable controller routes that also depend on host or non-URI version conditions are rejected at startup with `CONDITIONAL_ROUTE_SHADOW`; move them outside the auth mount. The integration checks the effective Express router flags because changing application settings after router creation does not change existing route matching. This is a tested Express 5 compatibility boundary, not an inspection of the router stack.
|
|
28
|
+
|
|
29
|
+
Fastify does not expose its configured `trustProxy` value through a public inspection API. The boot summary therefore reports proxy trust as `unknown`, and diagnostics that depend on the setting are unavailable. Client IP resolution still uses the native Fastify request. Configure proxy trust on your Nest Fastify adapter and verify it against your deployment topology.
|
|
30
|
+
|
|
31
|
+
GraphQL, WebSocket and microservice integrations have separate implementation and end-to-end acceptance gates. They will use optional entry points so applications can install the transports they use. HTTP platform tests do not establish that those integrations are ready.
|
|
32
|
+
|
|
33
|
+
Start with the [design workspace](docs/design/README.md), [reviewed specification](docs/design/design-v7.md), [review ledger](docs/design/ledger.md) and [implementation plan](../../docs/superpowers/plans/2026-09-21-better-auth.md). Independent Better Auth, NestJS and security reviews approved the final v7 snapshot after resolving the round-6 and round-7 findings. The remaining entry points are tracked in [issue #534](https://github.com/thilllon/nestjs-kit/issues/534).
|
|
34
|
+
|
|
35
|
+
## Develop in this monorepo
|
|
36
|
+
|
|
37
|
+
From the repository root:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
mise install
|
|
41
|
+
mise exec -- pnpm install
|
|
42
|
+
mise exec -- pnpm --filter nestjs-slightly-better-auth typecheck
|
|
43
|
+
mise exec -- pnpm --filter nestjs-slightly-better-auth build
|
|
44
|
+
mise exec -- pnpm test:packaging
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The shared toolchain uses Node.js LTS, pnpm, tsdown, TypeScript and Vitest. Builds produce ESM and CommonJS artifacts with declarations. The `module-sync` export makes supported Node `import` and `require()` consumers share the same ESM module identity.
|
|
48
|
+
|
|
49
|
+
[Contributing](../../CONTRIBUTING.md) explains repository checks and PRs. See [package instructions](AGENTS.md) and [import provenance](docs/provenance.md) before working with the historical material.
|
|
50
|
+
|
|
51
|
+
## Releases
|
|
52
|
+
|
|
53
|
+
Versions come from Changesets, and publication runs from the monorepo's release workflow with npm trusted publishing and provenance; see [release automation](../../docs/releases.md). New entry points are additive, so they ship as minor versions.
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
[MIT](LICENSE). The archived upstream adapter retains its [original MIT notice](legacy/LICENSE).
|
package/dist/admin.cjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_auth_errors = require("./auth-errors-CsERBNWI.cjs");
|
|
3
|
+
const require_auth_decorators = require("./auth-decorators-CwW5JY4y.cjs");
|
|
4
|
+
let better_auth_api = require("better-auth/api");
|
|
5
|
+
//#region src/admin.ts
|
|
6
|
+
const adminPermissionPolicy = {
|
|
7
|
+
id: "better-auth:admin/permission",
|
|
8
|
+
requires: {
|
|
9
|
+
plugins: ["admin"],
|
|
10
|
+
principals: ["session"],
|
|
11
|
+
freshIdentity: true,
|
|
12
|
+
hostlessCalls: true
|
|
13
|
+
},
|
|
14
|
+
validate({ permissions }) {
|
|
15
|
+
if (!Object.keys(permissions).length || Object.values(permissions).some((actions) => !Array.isArray(actions) || !actions.length)) throw new require_auth_errors.BetterAuthConfigurationError("EMPTY_PERMISSIONS", "Admin permissions must contain a resource and actions.");
|
|
16
|
+
},
|
|
17
|
+
async evaluate({ permissions }, context) {
|
|
18
|
+
const { principal } = context;
|
|
19
|
+
if (!principal.userId) return {
|
|
20
|
+
effect: "deny",
|
|
21
|
+
reason: "USER_REQUIRED"
|
|
22
|
+
};
|
|
23
|
+
const userId = principal.userId;
|
|
24
|
+
let role;
|
|
25
|
+
if (principal.delegation) {
|
|
26
|
+
const owner = await context.memo(JSON.stringify(["admin:owner", userId]), async () => (await context.auth.context()).internalAdapter.findUserById(userId));
|
|
27
|
+
if (!owner) return {
|
|
28
|
+
effect: "deny",
|
|
29
|
+
status: 401,
|
|
30
|
+
reason: "USER_NOT_FOUND"
|
|
31
|
+
};
|
|
32
|
+
if (owner.banned && (!owner.banExpires || new Date(owner.banExpires).getTime() > Date.now())) return {
|
|
33
|
+
effect: "deny",
|
|
34
|
+
status: 401,
|
|
35
|
+
reason: "USER_BANNED"
|
|
36
|
+
};
|
|
37
|
+
if (typeof owner.role === "string" && owner.role.length) role = owner.role;
|
|
38
|
+
}
|
|
39
|
+
const stablePermissions = Object.entries(permissions).sort(([a], [b]) => a.localeCompare(b)).map(([resource, actions]) => [resource, [...actions].sort()]);
|
|
40
|
+
try {
|
|
41
|
+
if (!(await context.memo(JSON.stringify([
|
|
42
|
+
"admin:userHasPermission",
|
|
43
|
+
userId,
|
|
44
|
+
role ?? null,
|
|
45
|
+
stablePermissions
|
|
46
|
+
]), () => context.auth.run({
|
|
47
|
+
cookies: context.cookies,
|
|
48
|
+
inbound: () => context.headers,
|
|
49
|
+
internal: true
|
|
50
|
+
}, () => context.auth.api.userHasPermission({ body: {
|
|
51
|
+
userId,
|
|
52
|
+
permissions,
|
|
53
|
+
...role === void 0 ? {} : { role }
|
|
54
|
+
} })))).success) return {
|
|
55
|
+
effect: "deny",
|
|
56
|
+
reason: "MISSING_PERMISSION"
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if ((0, better_auth_api.isAPIError)(error) && error.statusCode === 400 && error.body?.message === "user not found") return {
|
|
60
|
+
effect: "deny",
|
|
61
|
+
status: 401,
|
|
62
|
+
reason: "USER_NOT_FOUND"
|
|
63
|
+
};
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
if (principal.delegation && !principal.delegation.allows(permissions)) return {
|
|
67
|
+
effect: "deny",
|
|
68
|
+
reason: "INSUFFICIENT_SCOPE"
|
|
69
|
+
};
|
|
70
|
+
return { effect: "allow" };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function permission(permissions, options = {}) {
|
|
74
|
+
return {
|
|
75
|
+
policy: adminPermissionPolicy,
|
|
76
|
+
params: { permissions },
|
|
77
|
+
...options.principals ? { principals: options.principals } : {}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function RequirePermission(permissions, options) {
|
|
81
|
+
return require_auth_decorators.Require(permission(permissions, options));
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
exports.RequirePermission = RequirePermission;
|
|
85
|
+
exports.adminPermissionPolicy = adminPermissionPolicy;
|
|
86
|
+
exports.permission = permission;
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=admin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.cjs","names":["BetterAuthConfigurationError","isAPIError","Require"],"sources":["../src/admin.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport type { AuthorizationPolicy, Requirement } from \"./auth-contracts.js\";\nimport { Require } from \"./auth-decorators.js\";\nimport { BetterAuthConfigurationError } from \"./auth-errors.js\";\nimport type {\n AdminPermissions,\n AuthPrincipal,\n PrincipalKind,\n} from \"./auth-types.js\";\nexport type { AdminPermissions } from \"./auth-types.js\";\nexport interface PermissionOptions {\n principals?: readonly PrincipalKind[];\n}\nexport interface AdminPermissionParams {\n readonly permissions: AdminPermissions;\n}\ninterface AdminApi {\n userHasPermission(input: {\n body: {\n userId: string;\n role?: string;\n permissions: Readonly<Record<string, readonly string[]>>;\n };\n }): Promise<{ success: boolean }>;\n}\nexport const adminPermissionPolicy: AuthorizationPolicy<\n AdminPermissionParams,\n AuthPrincipal\n> = {\n id: \"better-auth:admin/permission\",\n requires: {\n plugins: [\"admin\"],\n principals: [\"session\"],\n freshIdentity: true,\n hostlessCalls: true,\n },\n validate({ permissions }) {\n if (\n !Object.keys(permissions).length ||\n Object.values(permissions).some(\n (actions) => !Array.isArray(actions) || !actions.length,\n )\n ) {\n throw new BetterAuthConfigurationError(\n \"EMPTY_PERMISSIONS\",\n \"Admin permissions must contain a resource and actions.\",\n );\n }\n },\n async evaluate({ permissions }, context) {\n const { principal } = context;\n if (!principal.userId) {\n return { effect: \"deny\", reason: \"USER_REQUIRED\" };\n }\n const userId = principal.userId;\n let role: string | undefined;\n if (principal.delegation) {\n const owner = await context.memo(\n JSON.stringify([\"admin:owner\", userId]),\n async () =>\n (await context.auth.context()).internalAdapter.findUserById(userId),\n );\n if (!owner) {\n return { effect: \"deny\", status: 401, reason: \"USER_NOT_FOUND\" };\n }\n if (\n owner.banned &&\n (!owner.banExpires || new Date(owner.banExpires).getTime() > Date.now())\n ) {\n return { effect: \"deny\", status: 401, reason: \"USER_BANNED\" };\n }\n if (typeof owner.role === \"string\" && owner.role.length) {\n role = owner.role;\n }\n }\n const stablePermissions = Object.entries(permissions)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([resource, actions]) => [resource, [...actions].sort()]);\n try {\n const result = await context.memo(\n JSON.stringify([\n \"admin:userHasPermission\",\n userId,\n role ?? null,\n stablePermissions,\n ]),\n () =>\n context.auth.run(\n {\n cookies: context.cookies,\n inbound: () => context.headers,\n internal: true,\n },\n () =>\n (context.auth.api as unknown as AdminApi).userHasPermission({\n body: {\n userId,\n permissions,\n ...(role === undefined ? {} : { role }),\n },\n }),\n ),\n );\n if (!result.success) {\n return { effect: \"deny\", reason: \"MISSING_PERMISSION\" };\n }\n } catch (error) {\n if (\n isAPIError(error) &&\n error.statusCode === 400 &&\n error.body?.message === \"user not found\"\n ) {\n return { effect: \"deny\", status: 401, reason: \"USER_NOT_FOUND\" };\n }\n throw error;\n }\n if (principal.delegation && !principal.delegation.allows(permissions)) {\n return { effect: \"deny\", reason: \"INSUFFICIENT_SCOPE\" };\n }\n return { effect: \"allow\" };\n },\n};\nexport function permission<N extends string = \"default\">(\n permissions: AdminPermissions<N>,\n options: PermissionOptions = {},\n): Requirement<AdminPermissionParams> {\n return {\n policy: adminPermissionPolicy,\n params: { permissions: permissions as AdminPermissions },\n ...(options.principals ? { principals: options.principals } : {}),\n };\n}\nexport function RequirePermission<N extends string = \"default\">(\n permissions: AdminPermissions<N>,\n options?: PermissionOptions,\n): ClassDecorator & MethodDecorator {\n return Require(permission<N>(permissions, options));\n}\n"],"mappings":";;;;;AAyBA,MAAa,wBAGT;CACF,IAAI;CACJ,UAAU;EACR,SAAS,CAAC,OAAO;EACjB,YAAY,CAAC,SAAS;EACtB,eAAe;EACf,eAAe;CACjB;CACA,SAAS,EAAE,eAAe;EACxB,IACE,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC,UAC1B,OAAO,OAAO,WAAW,CAAC,CAAC,MACxB,YAAY,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,MACnD,GAEA,MAAM,IAAIA,oBAAAA,6BACR,qBACA,wDACF;CAEJ;CACA,MAAM,SAAS,EAAE,eAAe,SAAS;EACvC,MAAM,EAAE,cAAc;EACtB,IAAI,CAAC,UAAU,QACb,OAAO;GAAE,QAAQ;GAAQ,QAAQ;EAAgB;EAEnD,MAAM,SAAS,UAAU;EACzB,IAAI;EACJ,IAAI,UAAU,YAAY;GACxB,MAAM,QAAQ,MAAM,QAAQ,KAC1B,KAAK,UAAU,CAAC,eAAe,MAAM,CAAC,GACtC,aACG,MAAM,QAAQ,KAAK,QAAQ,EAAA,CAAG,gBAAgB,aAAa,MAAM,CACtE;GACA,IAAI,CAAC,OACH,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAiB;GAEjE,IACE,MAAM,WACL,CAAC,MAAM,cAAc,IAAI,KAAK,MAAM,UAAU,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,IAEtE,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAc;GAE9D,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,QAC/C,OAAO,MAAM;EAEjB;EACA,MAAM,oBAAoB,OAAO,QAAQ,WAAW,CAAC,CAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CACtC,KAAK,CAAC,UAAU,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;EAC/D,IAAI;GAyBF,IAAI,EAAC,MAxBgB,QAAQ,KAC3B,KAAK,UAAU;IACb;IACA;IACA,QAAQ;IACR;GACF,CAAC,SAEC,QAAQ,KAAK,IACX;IACE,SAAS,QAAQ;IACjB,eAAe,QAAQ;IACvB,UAAU;GACZ,SAEG,QAAQ,KAAK,IAA4B,kBAAkB,EAC1D,MAAM;IACJ;IACA;IACA,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;GACvC,EACF,CAAC,CACL,CACJ,EAAA,CACY,SACV,OAAO;IAAE,QAAQ;IAAQ,QAAQ;GAAqB;EAE1D,SAAS,OAAO;GACd,KAAA,GACEC,gBAAAA,WAAAA,CAAW,KAAK,KAChB,MAAM,eAAe,OACrB,MAAM,MAAM,YAAY,kBAExB,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAiB;GAEjE,MAAM;EACR;EACA,IAAI,UAAU,cAAc,CAAC,UAAU,WAAW,OAAO,WAAW,GAClE,OAAO;GAAE,QAAQ;GAAQ,QAAQ;EAAqB;EAExD,OAAO,EAAE,QAAQ,QAAQ;CAC3B;AACF;AACA,SAAgB,WACd,aACA,UAA6B,CAAC,GACM;CACpC,OAAO;EACL,QAAQ;EACR,QAAQ,EAAe,YAAgC;EACvD,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;CACjE;AACF;AACA,SAAgB,kBACd,aACA,SACkC;CAClC,OAAOC,wBAAAA,QAAQ,WAAc,aAAa,OAAO,CAAC;AACpD"}
|
package/dist/admin.d.cts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { f as AuthorizationPolicy, kt as PrincipalKind, mt as AdminPermissions, nt as Requirement, yt as AuthPrincipal } from "./auth-contracts-BBA1C1gj.cjs";
|
|
2
|
+
//#region src/admin.d.ts
|
|
3
|
+
export interface PermissionOptions {
|
|
4
|
+
principals?: readonly PrincipalKind[];
|
|
5
|
+
}
|
|
6
|
+
export interface AdminPermissionParams {
|
|
7
|
+
readonly permissions: AdminPermissions;
|
|
8
|
+
}
|
|
9
|
+
export declare const adminPermissionPolicy: AuthorizationPolicy<AdminPermissionParams, AuthPrincipal>;
|
|
10
|
+
export declare function permission<N extends string = "default">(permissions: AdminPermissions<N>, options?: PermissionOptions): Requirement<AdminPermissionParams>;
|
|
11
|
+
export declare function RequirePermission<N extends string = "default">(permissions: AdminPermissions<N>, options?: PermissionOptions): ClassDecorator & MethodDecorator;
|
|
12
|
+
//#endregion
|
|
13
|
+
export type { AdminPermissions };
|
|
14
|
+
//# sourceMappingURL=admin.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.cts","names":[],"sources":["../src/admin.ts"],"mappings":";;iBAUiB;EACf,sBAAsB;;iBAEP;WACN,aAAa;;qBAWX,uBAAuB,oBAClC,uBACA;wBA+Fc,WAAW,8BACzB,aAAa,iBAAiB,IAC9B,UAAS,oBACR,YAAY;wBAOC,kBAAkB,8BAChC,aAAa,iBAAiB,IAC9B,UAAU,oBACT,iBAAiB"}
|
package/dist/admin.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { f as AuthorizationPolicy, kt as PrincipalKind, mt as AdminPermissions, nt as Requirement, yt as AuthPrincipal } from "./auth-contracts-tqsfBZ8B.mjs";
|
|
2
|
+
//#region src/admin.d.ts
|
|
3
|
+
export interface PermissionOptions {
|
|
4
|
+
principals?: readonly PrincipalKind[];
|
|
5
|
+
}
|
|
6
|
+
export interface AdminPermissionParams {
|
|
7
|
+
readonly permissions: AdminPermissions;
|
|
8
|
+
}
|
|
9
|
+
export declare const adminPermissionPolicy: AuthorizationPolicy<AdminPermissionParams, AuthPrincipal>;
|
|
10
|
+
export declare function permission<N extends string = "default">(permissions: AdminPermissions<N>, options?: PermissionOptions): Requirement<AdminPermissionParams>;
|
|
11
|
+
export declare function RequirePermission<N extends string = "default">(permissions: AdminPermissions<N>, options?: PermissionOptions): ClassDecorator & MethodDecorator;
|
|
12
|
+
//#endregion
|
|
13
|
+
export type { AdminPermissions };
|
|
14
|
+
//# sourceMappingURL=admin.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.mts","names":[],"sources":["../src/admin.ts"],"mappings":";;iBAUiB;EACf,sBAAsB;;iBAEP;WACN,aAAa;;qBAWX,uBAAuB,oBAClC,uBACA;wBA+Fc,WAAW,8BACzB,aAAa,iBAAiB,IAC9B,UAAS,oBACR,YAAY;wBAOC,kBAAkB,8BAChC,aAAa,iBAAiB,IAC9B,UAAU,oBACT,iBAAiB"}
|
package/dist/admin.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { n as BetterAuthConfigurationError } from "./auth-errors-CQjfgjaO.mjs";
|
|
2
|
+
import { u as Require } from "./auth-decorators-DfAJbHar.mjs";
|
|
3
|
+
import { isAPIError } from "better-auth/api";
|
|
4
|
+
//#region src/admin.ts
|
|
5
|
+
const adminPermissionPolicy = {
|
|
6
|
+
id: "better-auth:admin/permission",
|
|
7
|
+
requires: {
|
|
8
|
+
plugins: ["admin"],
|
|
9
|
+
principals: ["session"],
|
|
10
|
+
freshIdentity: true,
|
|
11
|
+
hostlessCalls: true
|
|
12
|
+
},
|
|
13
|
+
validate({ permissions }) {
|
|
14
|
+
if (!Object.keys(permissions).length || Object.values(permissions).some((actions) => !Array.isArray(actions) || !actions.length)) throw new BetterAuthConfigurationError("EMPTY_PERMISSIONS", "Admin permissions must contain a resource and actions.");
|
|
15
|
+
},
|
|
16
|
+
async evaluate({ permissions }, context) {
|
|
17
|
+
const { principal } = context;
|
|
18
|
+
if (!principal.userId) return {
|
|
19
|
+
effect: "deny",
|
|
20
|
+
reason: "USER_REQUIRED"
|
|
21
|
+
};
|
|
22
|
+
const userId = principal.userId;
|
|
23
|
+
let role;
|
|
24
|
+
if (principal.delegation) {
|
|
25
|
+
const owner = await context.memo(JSON.stringify(["admin:owner", userId]), async () => (await context.auth.context()).internalAdapter.findUserById(userId));
|
|
26
|
+
if (!owner) return {
|
|
27
|
+
effect: "deny",
|
|
28
|
+
status: 401,
|
|
29
|
+
reason: "USER_NOT_FOUND"
|
|
30
|
+
};
|
|
31
|
+
if (owner.banned && (!owner.banExpires || new Date(owner.banExpires).getTime() > Date.now())) return {
|
|
32
|
+
effect: "deny",
|
|
33
|
+
status: 401,
|
|
34
|
+
reason: "USER_BANNED"
|
|
35
|
+
};
|
|
36
|
+
if (typeof owner.role === "string" && owner.role.length) role = owner.role;
|
|
37
|
+
}
|
|
38
|
+
const stablePermissions = Object.entries(permissions).sort(([a], [b]) => a.localeCompare(b)).map(([resource, actions]) => [resource, [...actions].sort()]);
|
|
39
|
+
try {
|
|
40
|
+
if (!(await context.memo(JSON.stringify([
|
|
41
|
+
"admin:userHasPermission",
|
|
42
|
+
userId,
|
|
43
|
+
role ?? null,
|
|
44
|
+
stablePermissions
|
|
45
|
+
]), () => context.auth.run({
|
|
46
|
+
cookies: context.cookies,
|
|
47
|
+
inbound: () => context.headers,
|
|
48
|
+
internal: true
|
|
49
|
+
}, () => context.auth.api.userHasPermission({ body: {
|
|
50
|
+
userId,
|
|
51
|
+
permissions,
|
|
52
|
+
...role === void 0 ? {} : { role }
|
|
53
|
+
} })))).success) return {
|
|
54
|
+
effect: "deny",
|
|
55
|
+
reason: "MISSING_PERMISSION"
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (isAPIError(error) && error.statusCode === 400 && error.body?.message === "user not found") return {
|
|
59
|
+
effect: "deny",
|
|
60
|
+
status: 401,
|
|
61
|
+
reason: "USER_NOT_FOUND"
|
|
62
|
+
};
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
if (principal.delegation && !principal.delegation.allows(permissions)) return {
|
|
66
|
+
effect: "deny",
|
|
67
|
+
reason: "INSUFFICIENT_SCOPE"
|
|
68
|
+
};
|
|
69
|
+
return { effect: "allow" };
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function permission(permissions, options = {}) {
|
|
73
|
+
return {
|
|
74
|
+
policy: adminPermissionPolicy,
|
|
75
|
+
params: { permissions },
|
|
76
|
+
...options.principals ? { principals: options.principals } : {}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function RequirePermission(permissions, options) {
|
|
80
|
+
return Require(permission(permissions, options));
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { RequirePermission, adminPermissionPolicy, permission };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=admin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport type { AuthorizationPolicy, Requirement } from \"./auth-contracts.js\";\nimport { Require } from \"./auth-decorators.js\";\nimport { BetterAuthConfigurationError } from \"./auth-errors.js\";\nimport type {\n AdminPermissions,\n AuthPrincipal,\n PrincipalKind,\n} from \"./auth-types.js\";\nexport type { AdminPermissions } from \"./auth-types.js\";\nexport interface PermissionOptions {\n principals?: readonly PrincipalKind[];\n}\nexport interface AdminPermissionParams {\n readonly permissions: AdminPermissions;\n}\ninterface AdminApi {\n userHasPermission(input: {\n body: {\n userId: string;\n role?: string;\n permissions: Readonly<Record<string, readonly string[]>>;\n };\n }): Promise<{ success: boolean }>;\n}\nexport const adminPermissionPolicy: AuthorizationPolicy<\n AdminPermissionParams,\n AuthPrincipal\n> = {\n id: \"better-auth:admin/permission\",\n requires: {\n plugins: [\"admin\"],\n principals: [\"session\"],\n freshIdentity: true,\n hostlessCalls: true,\n },\n validate({ permissions }) {\n if (\n !Object.keys(permissions).length ||\n Object.values(permissions).some(\n (actions) => !Array.isArray(actions) || !actions.length,\n )\n ) {\n throw new BetterAuthConfigurationError(\n \"EMPTY_PERMISSIONS\",\n \"Admin permissions must contain a resource and actions.\",\n );\n }\n },\n async evaluate({ permissions }, context) {\n const { principal } = context;\n if (!principal.userId) {\n return { effect: \"deny\", reason: \"USER_REQUIRED\" };\n }\n const userId = principal.userId;\n let role: string | undefined;\n if (principal.delegation) {\n const owner = await context.memo(\n JSON.stringify([\"admin:owner\", userId]),\n async () =>\n (await context.auth.context()).internalAdapter.findUserById(userId),\n );\n if (!owner) {\n return { effect: \"deny\", status: 401, reason: \"USER_NOT_FOUND\" };\n }\n if (\n owner.banned &&\n (!owner.banExpires || new Date(owner.banExpires).getTime() > Date.now())\n ) {\n return { effect: \"deny\", status: 401, reason: \"USER_BANNED\" };\n }\n if (typeof owner.role === \"string\" && owner.role.length) {\n role = owner.role;\n }\n }\n const stablePermissions = Object.entries(permissions)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([resource, actions]) => [resource, [...actions].sort()]);\n try {\n const result = await context.memo(\n JSON.stringify([\n \"admin:userHasPermission\",\n userId,\n role ?? null,\n stablePermissions,\n ]),\n () =>\n context.auth.run(\n {\n cookies: context.cookies,\n inbound: () => context.headers,\n internal: true,\n },\n () =>\n (context.auth.api as unknown as AdminApi).userHasPermission({\n body: {\n userId,\n permissions,\n ...(role === undefined ? {} : { role }),\n },\n }),\n ),\n );\n if (!result.success) {\n return { effect: \"deny\", reason: \"MISSING_PERMISSION\" };\n }\n } catch (error) {\n if (\n isAPIError(error) &&\n error.statusCode === 400 &&\n error.body?.message === \"user not found\"\n ) {\n return { effect: \"deny\", status: 401, reason: \"USER_NOT_FOUND\" };\n }\n throw error;\n }\n if (principal.delegation && !principal.delegation.allows(permissions)) {\n return { effect: \"deny\", reason: \"INSUFFICIENT_SCOPE\" };\n }\n return { effect: \"allow\" };\n },\n};\nexport function permission<N extends string = \"default\">(\n permissions: AdminPermissions<N>,\n options: PermissionOptions = {},\n): Requirement<AdminPermissionParams> {\n return {\n policy: adminPermissionPolicy,\n params: { permissions: permissions as AdminPermissions },\n ...(options.principals ? { principals: options.principals } : {}),\n };\n}\nexport function RequirePermission<N extends string = \"default\">(\n permissions: AdminPermissions<N>,\n options?: PermissionOptions,\n): ClassDecorator & MethodDecorator {\n return Require(permission<N>(permissions, options));\n}\n"],"mappings":";;;;AAyBA,MAAa,wBAGT;CACF,IAAI;CACJ,UAAU;EACR,SAAS,CAAC,OAAO;EACjB,YAAY,CAAC,SAAS;EACtB,eAAe;EACf,eAAe;CACjB;CACA,SAAS,EAAE,eAAe;EACxB,IACE,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC,UAC1B,OAAO,OAAO,WAAW,CAAC,CAAC,MACxB,YAAY,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,MACnD,GAEA,MAAM,IAAI,6BACR,qBACA,wDACF;CAEJ;CACA,MAAM,SAAS,EAAE,eAAe,SAAS;EACvC,MAAM,EAAE,cAAc;EACtB,IAAI,CAAC,UAAU,QACb,OAAO;GAAE,QAAQ;GAAQ,QAAQ;EAAgB;EAEnD,MAAM,SAAS,UAAU;EACzB,IAAI;EACJ,IAAI,UAAU,YAAY;GACxB,MAAM,QAAQ,MAAM,QAAQ,KAC1B,KAAK,UAAU,CAAC,eAAe,MAAM,CAAC,GACtC,aACG,MAAM,QAAQ,KAAK,QAAQ,EAAA,CAAG,gBAAgB,aAAa,MAAM,CACtE;GACA,IAAI,CAAC,OACH,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAiB;GAEjE,IACE,MAAM,WACL,CAAC,MAAM,cAAc,IAAI,KAAK,MAAM,UAAU,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,IAEtE,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAc;GAE9D,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,QAC/C,OAAO,MAAM;EAEjB;EACA,MAAM,oBAAoB,OAAO,QAAQ,WAAW,CAAC,CAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CACtC,KAAK,CAAC,UAAU,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;EAC/D,IAAI;GAyBF,IAAI,EAAC,MAxBgB,QAAQ,KAC3B,KAAK,UAAU;IACb;IACA;IACA,QAAQ;IACR;GACF,CAAC,SAEC,QAAQ,KAAK,IACX;IACE,SAAS,QAAQ;IACjB,eAAe,QAAQ;IACvB,UAAU;GACZ,SAEG,QAAQ,KAAK,IAA4B,kBAAkB,EAC1D,MAAM;IACJ;IACA;IACA,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;GACvC,EACF,CAAC,CACL,CACJ,EAAA,CACY,SACV,OAAO;IAAE,QAAQ;IAAQ,QAAQ;GAAqB;EAE1D,SAAS,OAAO;GACd,IACE,WAAW,KAAK,KAChB,MAAM,eAAe,OACrB,MAAM,MAAM,YAAY,kBAExB,OAAO;IAAE,QAAQ;IAAQ,QAAQ;IAAK,QAAQ;GAAiB;GAEjE,MAAM;EACR;EACA,IAAI,UAAU,cAAc,CAAC,UAAU,WAAW,OAAO,WAAW,GAClE,OAAO;GAAE,QAAQ;GAAQ,QAAQ;EAAqB;EAExD,OAAO,EAAE,QAAQ,QAAQ;CAC3B;AACF;AACA,SAAgB,WACd,aACA,UAA6B,CAAC,GACM;CACpC,OAAO;EACL,QAAQ;EACR,QAAQ,EAAe,YAAgC;EACvD,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;CACjE;AACF;AACA,SAAgB,kBACd,aACA,SACkC;CAClC,OAAO,QAAQ,WAAc,aAAa,OAAO,CAAC;AACpD"}
|
package/dist/api-key.cjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_auth_errors = require("./auth-errors-CsERBNWI.cjs");
|
|
3
|
+
const require_auth_decorators = require("./auth-decorators-CwW5JY4y.cjs");
|
|
4
|
+
let better_auth_plugins_access = require("better-auth/plugins/access");
|
|
5
|
+
//#region src/api-key.ts
|
|
6
|
+
const API_KEY_PRINCIPAL_KIND = "api-key";
|
|
7
|
+
function apiKeyPrincipal(options = {}) {
|
|
8
|
+
const header = options.header ?? "x-api-key";
|
|
9
|
+
const slowMs = typeof options.outageProbe === "object" ? options.outageProbe.slowMs ?? 500 : 500;
|
|
10
|
+
if (!header.trim() || slowMs !== false && (!Number.isFinite(slowMs) || slowMs < 0)) throw new require_auth_errors.BetterAuthConfigurationError("INVALID_API_KEY_OPTIONS", "API-key header must be nonempty and slowMs must be finite and nonnegative.");
|
|
11
|
+
const verificationMemo = Symbol("api-key-verification");
|
|
12
|
+
const probes = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
async function probe(auth) {
|
|
14
|
+
const previous = probes.get(auth.instance);
|
|
15
|
+
if (previous && (previous.pending || Date.now() - previous.at < 1e3)) return previous.result;
|
|
16
|
+
const result = (async () => {
|
|
17
|
+
const context = await auth.context();
|
|
18
|
+
const where = [{
|
|
19
|
+
field: "key",
|
|
20
|
+
value: crypto.randomUUID()
|
|
21
|
+
}];
|
|
22
|
+
await context.adapter.findOne({
|
|
23
|
+
model: "apikey",
|
|
24
|
+
where
|
|
25
|
+
});
|
|
26
|
+
await context.adapter.updateMany({
|
|
27
|
+
model: "apikey",
|
|
28
|
+
where,
|
|
29
|
+
update: { updatedAt: /* @__PURE__ */ new Date() }
|
|
30
|
+
});
|
|
31
|
+
})();
|
|
32
|
+
const state = {
|
|
33
|
+
at: Date.now(),
|
|
34
|
+
pending: true,
|
|
35
|
+
result
|
|
36
|
+
};
|
|
37
|
+
probes.set(auth.instance, state);
|
|
38
|
+
result.then(() => {
|
|
39
|
+
state.pending = false;
|
|
40
|
+
}, () => {
|
|
41
|
+
state.pending = false;
|
|
42
|
+
});
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
id: "better-auth:api-key",
|
|
47
|
+
kinds: [API_KEY_PRINCIPAL_KIND],
|
|
48
|
+
acceptance: options.acceptance ?? "explicit",
|
|
49
|
+
delegates: true,
|
|
50
|
+
credentialHeaders: [header],
|
|
51
|
+
effects: {
|
|
52
|
+
consumesQuota: true,
|
|
53
|
+
writes: true
|
|
54
|
+
},
|
|
55
|
+
requires: { plugins: ["api-key"] },
|
|
56
|
+
appliesTo: (request) => request.headers.has(header),
|
|
57
|
+
async resolve(request) {
|
|
58
|
+
if (!request.headers.has(header)) return require_auth_decorators.absent();
|
|
59
|
+
return request.memo(verificationMemo, async () => {
|
|
60
|
+
const key = request.headers.get(header);
|
|
61
|
+
const headers = new Headers();
|
|
62
|
+
for (const name of [
|
|
63
|
+
"host",
|
|
64
|
+
"x-forwarded-host",
|
|
65
|
+
"x-forwarded-proto"
|
|
66
|
+
]) {
|
|
67
|
+
const value = request.headers.get(name);
|
|
68
|
+
if (value !== null) headers.set(name, value);
|
|
69
|
+
}
|
|
70
|
+
const started = performance.now();
|
|
71
|
+
try {
|
|
72
|
+
const result = await request.auth.run({
|
|
73
|
+
cookies: request.cookies,
|
|
74
|
+
inbound: () => request.headers,
|
|
75
|
+
internal: true
|
|
76
|
+
}, () => request.auth.api.verifyApiKey({
|
|
77
|
+
body: {
|
|
78
|
+
key,
|
|
79
|
+
...options.configId === void 0 ? {} : { configId: options.configId }
|
|
80
|
+
},
|
|
81
|
+
headers
|
|
82
|
+
}));
|
|
83
|
+
if (result.valid) {
|
|
84
|
+
const verified = result.key;
|
|
85
|
+
if (!verified || typeof verified.id !== "string" || typeof verified.referenceId !== "string") throw new require_auth_errors.BetterAuthInfrastructureError(/* @__PURE__ */ new Error("Malformed API-key verification result"));
|
|
86
|
+
const configId = verified.configId ?? null;
|
|
87
|
+
const references = typeof options.references === "function" ? options.references({ configId }) : options.references ?? "user";
|
|
88
|
+
const permissions = verified.permissions ?? null;
|
|
89
|
+
const grant = (0, better_auth_plugins_access.role)(permissions ?? {});
|
|
90
|
+
return require_auth_decorators.authenticated({
|
|
91
|
+
kind: API_KEY_PRINCIPAL_KIND,
|
|
92
|
+
source: "better-auth:api-key",
|
|
93
|
+
keyId: verified.id,
|
|
94
|
+
configId,
|
|
95
|
+
referenceId: verified.referenceId,
|
|
96
|
+
userId: references === "user" ? verified.referenceId : null,
|
|
97
|
+
organizationId: references === "organization" ? verified.referenceId : null,
|
|
98
|
+
permissions,
|
|
99
|
+
delegation: {
|
|
100
|
+
description: "api-key permissions",
|
|
101
|
+
allows: (requested) => grant.authorize(requested).success
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const code = result.error?.code;
|
|
106
|
+
if (code === "RATE_LIMITED" || code === "USAGE_EXCEEDED") {
|
|
107
|
+
const retry = result.error?.details?.tryAgainIn;
|
|
108
|
+
return require_auth_decorators.rejected(require_auth_errors.AuthFailures.rejected({
|
|
109
|
+
status: 429,
|
|
110
|
+
reason: code,
|
|
111
|
+
...code === "RATE_LIMITED" && typeof retry === "number" ? { retryAfterSeconds: retry / 1e3 } : {}
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
if (code === "INVALID_API_KEY" && options.outageProbe !== false) {
|
|
115
|
+
if (slowMs !== false && performance.now() - started > slowMs) throw new require_auth_errors.BetterAuthInfrastructureError(/* @__PURE__ */ new Error("API-key verification exceeded the outage threshold"));
|
|
116
|
+
await probe(request.auth);
|
|
117
|
+
}
|
|
118
|
+
if (code === "INVALID_API_KEY" || code === "KEY_NOT_FOUND" || code === "KEY_DISABLED" || code === "KEY_EXPIRED") return require_auth_decorators.rejected(require_auth_errors.AuthFailures.rejected({
|
|
119
|
+
status: 401,
|
|
120
|
+
reason: code
|
|
121
|
+
}));
|
|
122
|
+
throw new require_auth_errors.BetterAuthInfrastructureError(/* @__PURE__ */ new Error(`Unexpected API-key verification error: ${code ?? "missing code"}`));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const failure = require_auth_errors.normalizeThrown(error, "source", "better-auth:api-key", [key]);
|
|
125
|
+
if ("sessionLoss" in failure) throw new require_auth_errors.BetterAuthInfrastructureError(error, { secrets: [key] });
|
|
126
|
+
return require_auth_decorators.rejected(failure);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const apiKeyPermissionPolicy = {
|
|
133
|
+
id: "better-auth:api-key/permission",
|
|
134
|
+
requires: {
|
|
135
|
+
plugins: ["api-key"],
|
|
136
|
+
principals: [API_KEY_PRINCIPAL_KIND]
|
|
137
|
+
},
|
|
138
|
+
validate(permissions) {
|
|
139
|
+
if (!Object.keys(permissions).length || Object.values(permissions).some((actions) => !Array.isArray(actions) || !actions.length)) throw new require_auth_errors.BetterAuthConfigurationError("EMPTY_PERMISSIONS", "API-key permissions must contain a resource and actions.");
|
|
140
|
+
},
|
|
141
|
+
evaluate(permissions, context) {
|
|
142
|
+
return context.principal.delegation.allows(permissions) ? { effect: "allow" } : {
|
|
143
|
+
effect: "deny",
|
|
144
|
+
reason: "MISSING_PERMISSION"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function apiKeyPermission(permissions) {
|
|
149
|
+
return {
|
|
150
|
+
policy: apiKeyPermissionPolicy,
|
|
151
|
+
params: permissions
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function RequireApiKeyPermission(permissions) {
|
|
155
|
+
return require_auth_decorators.Require(apiKeyPermission(permissions));
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
exports.API_KEY_PRINCIPAL_KIND = API_KEY_PRINCIPAL_KIND;
|
|
159
|
+
exports.RequireApiKeyPermission = RequireApiKeyPermission;
|
|
160
|
+
exports.apiKeyPermission = apiKeyPermission;
|
|
161
|
+
exports.apiKeyPrincipal = apiKeyPrincipal;
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=api-key.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.cjs","names":["BetterAuthConfigurationError","absent","BetterAuthInfrastructureError","role","authenticated","rejected","AuthFailures","normalizeThrown","Require"],"sources":["../src/api-key.ts"],"sourcesContent":["import { role } from \"better-auth/plugins/access\";\nimport type {\n AuthHandle,\n AuthPrincipalBase,\n AuthorizationPolicy,\n PrincipalDelegation,\n PrincipalSource,\n Requirement,\n} from \"./auth-contracts.js\";\nimport { Require } from \"./auth-decorators.js\";\nimport {\n AuthFailures,\n BetterAuthConfigurationError,\n BetterAuthInfrastructureError,\n normalizeThrown,\n} from \"./auth-errors.js\";\nimport { absent, authenticated, rejected } from \"./principal-resolver.js\";\n\nexport const API_KEY_PRINCIPAL_KIND = \"api-key\" as const;\nexport interface ApiKeyPrincipal extends AuthPrincipalBase {\n readonly kind: \"api-key\";\n readonly keyId: string;\n readonly configId: string | null;\n readonly referenceId: string;\n readonly userId: string | null;\n readonly organizationId: string | null;\n readonly permissions: Readonly<Record<string, readonly string[]>> | null;\n readonly delegation: PrincipalDelegation;\n}\n// The public module identity survives declaration bundling in both output formats.\ndeclare module \"nestjs-slightly-better-auth\" {\n interface PrincipalKinds {\n \"api-key\": ApiKeyPrincipal;\n }\n}\nexport interface ApiKeyPrincipalOptions {\n header?: string;\n configId?: string;\n references?:\n | \"user\"\n | \"organization\"\n | ((key: { readonly configId: string | null }) => \"user\" | \"organization\");\n /**\n * Default true: slow INVALID_API_KEY results (>500 ms) throw infrastructure errors,\n * then fast failures share one read/no-op-write probe per second per instance.\n * false disables both checks; { slowMs: false } keeps only the probe.\n * Set slowMs below pool lock/statement timeouts and above custom hashing costs.\n * Fast failures confined to one row, or secondary storage without database fallback,\n * may still look like invalid keys. A statement-level trigger can observe the zero-row write.\n */\n outageProbe?: boolean | { readonly slowMs?: number | false };\n acceptance?: \"default\" | \"explicit\";\n}\ninterface VerifiedKey {\n id: string;\n configId?: string | null;\n referenceId: string;\n permissions?: Record<string, readonly string[]> | null;\n}\ninterface Verification {\n valid: boolean;\n key?: VerifiedKey | null;\n error?: { code?: string; details?: { tryAgainIn?: number } } | null;\n}\ninterface KeyApi {\n verifyApiKey(input: {\n body: { key: string; configId?: string };\n headers: Headers;\n }): Promise<Verification>;\n}\nexport function apiKeyPrincipal(\n options: ApiKeyPrincipalOptions = {},\n): PrincipalSource<ApiKeyPrincipal> {\n const header = options.header ?? \"x-api-key\";\n const slowMs =\n typeof options.outageProbe === \"object\"\n ? (options.outageProbe.slowMs ?? 500)\n : 500;\n if (\n !header.trim() ||\n (slowMs !== false && (!Number.isFinite(slowMs) || slowMs < 0))\n ) {\n throw new BetterAuthConfigurationError(\n \"INVALID_API_KEY_OPTIONS\",\n \"API-key header must be nonempty and slowMs must be finite and nonnegative.\",\n );\n }\n const verificationMemo = Symbol(\"api-key-verification\");\n const probes = new WeakMap<\n object,\n { at: number; pending: boolean; result: Promise<void> }\n >();\n async function probe(auth: AuthHandle): Promise<void> {\n const previous = probes.get(auth.instance);\n if (previous && (previous.pending || Date.now() - previous.at < 1000)) {\n return previous.result;\n }\n const result = (async () => {\n const context = await auth.context();\n // The logical key column is string-valued for default, UUID and serial IDs.\n const where = [{ field: \"key\", value: crypto.randomUUID() }];\n await context.adapter.findOne({ model: \"apikey\", where });\n await context.adapter.updateMany({\n model: \"apikey\",\n where,\n update: { updatedAt: new Date() },\n });\n })();\n const state = { at: Date.now(), pending: true, result };\n probes.set(auth.instance, state);\n void result.then(\n () => {\n state.pending = false;\n },\n () => {\n state.pending = false;\n },\n );\n return result;\n }\n return {\n id: \"better-auth:api-key\",\n kinds: [API_KEY_PRINCIPAL_KIND],\n acceptance: options.acceptance ?? \"explicit\",\n delegates: true,\n credentialHeaders: [header],\n effects: { consumesQuota: true, writes: true },\n requires: { plugins: [\"api-key\"] },\n appliesTo: (request) => request.headers.has(header),\n async resolve(request) {\n if (!request.headers.has(header)) {\n return absent();\n }\n return request.memo(verificationMemo, async () => {\n const key = request.headers.get(header)!;\n const headers = new Headers();\n for (const name of [\"host\", \"x-forwarded-host\", \"x-forwarded-proto\"]) {\n const value = request.headers.get(name);\n if (value !== null) {\n headers.set(name, value);\n }\n }\n const started = performance.now();\n try {\n const result = await request.auth.run(\n {\n cookies: request.cookies,\n inbound: () => request.headers,\n internal: true,\n },\n () =>\n (request.auth.api as unknown as KeyApi).verifyApiKey({\n body: {\n key,\n ...(options.configId === undefined\n ? {}\n : { configId: options.configId }),\n },\n headers,\n }),\n );\n if (result.valid) {\n const verified = result.key;\n if (\n !verified ||\n typeof verified.id !== \"string\" ||\n typeof verified.referenceId !== \"string\"\n ) {\n throw new BetterAuthInfrastructureError(\n new Error(\"Malformed API-key verification result\"),\n );\n }\n const configId = verified.configId ?? null;\n const references =\n typeof options.references === \"function\"\n ? options.references({ configId })\n : (options.references ?? \"user\");\n const permissions = verified.permissions ?? null;\n const grant = role(permissions ?? {});\n return authenticated({\n kind: API_KEY_PRINCIPAL_KIND,\n source: \"better-auth:api-key\",\n keyId: verified.id,\n configId,\n referenceId: verified.referenceId,\n userId: references === \"user\" ? verified.referenceId : null,\n organizationId:\n references === \"organization\" ? verified.referenceId : null,\n permissions,\n delegation: {\n description: \"api-key permissions\",\n allows: (requested) => grant.authorize(requested).success,\n },\n });\n }\n const code = result.error?.code;\n if (code === \"RATE_LIMITED\" || code === \"USAGE_EXCEEDED\") {\n const retry = result.error?.details?.tryAgainIn;\n return rejected(\n AuthFailures.rejected({\n status: 429,\n reason: code,\n ...(code === \"RATE_LIMITED\" && typeof retry === \"number\"\n ? { retryAfterSeconds: retry / 1000 }\n : {}),\n }),\n );\n }\n if (code === \"INVALID_API_KEY\" && options.outageProbe !== false) {\n if (slowMs !== false && performance.now() - started > slowMs) {\n throw new BetterAuthInfrastructureError(\n new Error(\"API-key verification exceeded the outage threshold\"),\n );\n }\n await probe(request.auth);\n }\n if (\n code === \"INVALID_API_KEY\" ||\n code === \"KEY_NOT_FOUND\" ||\n code === \"KEY_DISABLED\" ||\n code === \"KEY_EXPIRED\"\n ) {\n return rejected(\n AuthFailures.rejected({ status: 401, reason: code }),\n );\n }\n throw new BetterAuthInfrastructureError(\n new Error(\n `Unexpected API-key verification error: ${code ?? \"missing code\"}`,\n ),\n );\n } catch (error) {\n const failure = normalizeThrown(\n error,\n \"source\",\n \"better-auth:api-key\",\n [key],\n );\n if (\"sessionLoss\" in failure) {\n throw new BetterAuthInfrastructureError(error, { secrets: [key] });\n }\n return rejected(failure);\n }\n });\n },\n };\n}\nconst apiKeyPermissionPolicy: AuthorizationPolicy<\n Record<string, string[]>,\n ApiKeyPrincipal\n> = {\n id: \"better-auth:api-key/permission\",\n requires: { plugins: [\"api-key\"], principals: [API_KEY_PRINCIPAL_KIND] },\n validate(permissions) {\n if (\n !Object.keys(permissions).length ||\n Object.values(permissions).some(\n (actions) => !Array.isArray(actions) || !actions.length,\n )\n ) {\n throw new BetterAuthConfigurationError(\n \"EMPTY_PERMISSIONS\",\n \"API-key permissions must contain a resource and actions.\",\n );\n }\n },\n evaluate(permissions, context) {\n return context.principal.delegation.allows(permissions)\n ? { effect: \"allow\" }\n : { effect: \"deny\", reason: \"MISSING_PERMISSION\" };\n },\n};\nexport function apiKeyPermission(\n permissions: Record<string, string[]>,\n): Requirement<Record<string, string[]>> {\n return { policy: apiKeyPermissionPolicy, params: permissions };\n}\nexport function RequireApiKeyPermission(\n permissions: Record<string, string[]>,\n): ClassDecorator & MethodDecorator {\n return Require(apiKeyPermission(permissions));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Y as PrincipalDelegation, et as PrincipalSource, nt as Requirement, o as AuthPrincipalBase } from "./auth-contracts-BBA1C1gj.cjs";
|
|
2
|
+
//#region src/api-key.d.ts
|
|
3
|
+
export declare const API_KEY_PRINCIPAL_KIND: "api-key";
|
|
4
|
+
export interface ApiKeyPrincipal extends AuthPrincipalBase {
|
|
5
|
+
readonly kind: "api-key";
|
|
6
|
+
readonly keyId: string;
|
|
7
|
+
readonly configId: string | null;
|
|
8
|
+
readonly referenceId: string;
|
|
9
|
+
readonly userId: string | null;
|
|
10
|
+
readonly organizationId: string | null;
|
|
11
|
+
readonly permissions: Readonly<Record<string, readonly string[]>> | null;
|
|
12
|
+
readonly delegation: PrincipalDelegation;
|
|
13
|
+
}
|
|
14
|
+
declare module "nestjs-slightly-better-auth" {
|
|
15
|
+
interface PrincipalKinds {
|
|
16
|
+
"api-key": ApiKeyPrincipal;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export interface ApiKeyPrincipalOptions {
|
|
20
|
+
header?: string;
|
|
21
|
+
configId?: string;
|
|
22
|
+
references?: "user" | "organization" | ((key: {
|
|
23
|
+
readonly configId: string | null;
|
|
24
|
+
}) => "user" | "organization");
|
|
25
|
+
/**
|
|
26
|
+
* Default true: slow INVALID_API_KEY results (>500 ms) throw infrastructure errors,
|
|
27
|
+
* then fast failures share one read/no-op-write probe per second per instance.
|
|
28
|
+
* false disables both checks; { slowMs: false } keeps only the probe.
|
|
29
|
+
* Set slowMs below pool lock/statement timeouts and above custom hashing costs.
|
|
30
|
+
* Fast failures confined to one row, or secondary storage without database fallback,
|
|
31
|
+
* may still look like invalid keys. A statement-level trigger can observe the zero-row write.
|
|
32
|
+
*/
|
|
33
|
+
outageProbe?: boolean | {
|
|
34
|
+
readonly slowMs?: number | false;
|
|
35
|
+
};
|
|
36
|
+
acceptance?: "default" | "explicit";
|
|
37
|
+
}
|
|
38
|
+
export declare function apiKeyPrincipal(options?: ApiKeyPrincipalOptions): PrincipalSource<ApiKeyPrincipal>;
|
|
39
|
+
export declare function apiKeyPermission(permissions: Record<string, string[]>): Requirement<Record<string, string[]>>;
|
|
40
|
+
export declare function RequireApiKeyPermission(permissions: Record<string, string[]>): ClassDecorator & MethodDecorator;
|
|
41
|
+
//#endregion
|
|
42
|
+
//# sourceMappingURL=api-key.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.cts","names":[],"sources":["../src/api-key.ts"],"mappings":";;qBAkBa;iBACI,wBAAwB;WAC9B;WACA;WACA;WACA;WACA;WACA;WACA,aAAa,SAAS;WACtB,YAAY;;;YAIX;IACR,WAAW;;;iBAGE;EACf;EACA;EACA,yCAGM;aAAgB;;;;;;;;;;EAStB;aAAmC;;EACnC;;wBAmBc,gBACd,UAAS,yBACR,gBAAgB;wBAwMH,iBACd,aAAa,2BACZ,YAAY;wBAGC,wBACd,aAAa,2BACZ,iBAAiB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Y as PrincipalDelegation, et as PrincipalSource, nt as Requirement, o as AuthPrincipalBase } from "./auth-contracts-tqsfBZ8B.mjs";
|
|
2
|
+
//#region src/api-key.d.ts
|
|
3
|
+
export declare const API_KEY_PRINCIPAL_KIND: "api-key";
|
|
4
|
+
export interface ApiKeyPrincipal extends AuthPrincipalBase {
|
|
5
|
+
readonly kind: "api-key";
|
|
6
|
+
readonly keyId: string;
|
|
7
|
+
readonly configId: string | null;
|
|
8
|
+
readonly referenceId: string;
|
|
9
|
+
readonly userId: string | null;
|
|
10
|
+
readonly organizationId: string | null;
|
|
11
|
+
readonly permissions: Readonly<Record<string, readonly string[]>> | null;
|
|
12
|
+
readonly delegation: PrincipalDelegation;
|
|
13
|
+
}
|
|
14
|
+
declare module "nestjs-slightly-better-auth" {
|
|
15
|
+
interface PrincipalKinds {
|
|
16
|
+
"api-key": ApiKeyPrincipal;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export interface ApiKeyPrincipalOptions {
|
|
20
|
+
header?: string;
|
|
21
|
+
configId?: string;
|
|
22
|
+
references?: "user" | "organization" | ((key: {
|
|
23
|
+
readonly configId: string | null;
|
|
24
|
+
}) => "user" | "organization");
|
|
25
|
+
/**
|
|
26
|
+
* Default true: slow INVALID_API_KEY results (>500 ms) throw infrastructure errors,
|
|
27
|
+
* then fast failures share one read/no-op-write probe per second per instance.
|
|
28
|
+
* false disables both checks; { slowMs: false } keeps only the probe.
|
|
29
|
+
* Set slowMs below pool lock/statement timeouts and above custom hashing costs.
|
|
30
|
+
* Fast failures confined to one row, or secondary storage without database fallback,
|
|
31
|
+
* may still look like invalid keys. A statement-level trigger can observe the zero-row write.
|
|
32
|
+
*/
|
|
33
|
+
outageProbe?: boolean | {
|
|
34
|
+
readonly slowMs?: number | false;
|
|
35
|
+
};
|
|
36
|
+
acceptance?: "default" | "explicit";
|
|
37
|
+
}
|
|
38
|
+
export declare function apiKeyPrincipal(options?: ApiKeyPrincipalOptions): PrincipalSource<ApiKeyPrincipal>;
|
|
39
|
+
export declare function apiKeyPermission(permissions: Record<string, string[]>): Requirement<Record<string, string[]>>;
|
|
40
|
+
export declare function RequireApiKeyPermission(permissions: Record<string, string[]>): ClassDecorator & MethodDecorator;
|
|
41
|
+
//#endregion
|
|
42
|
+
//# sourceMappingURL=api-key.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.mts","names":[],"sources":["../src/api-key.ts"],"mappings":";;qBAkBa;iBACI,wBAAwB;WAC9B;WACA;WACA;WACA;WACA;WACA;WACA,aAAa,SAAS;WACtB,YAAY;;;YAIX;IACR,WAAW;;;iBAGE;EACf;EACA;EACA,yCAGM;aAAgB;;;;;;;;;;EAStB;aAAmC;;EACnC;;wBAmBc,gBACd,UAAS,yBACR,gBAAgB;wBAwMH,iBACd,aAAa,2BACZ,YAAY;wBAGC,wBACd,aAAa,2BACZ,iBAAiB"}
|