@gnldev/auth 0.1.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 +201 -0
- package/README.md +138 -0
- package/dist/adapter.d.ts +38 -0
- package/dist/adapter.js +79 -0
- package/dist/adapter.js.map +1 -0
- package/dist/gate.d.ts +32 -0
- package/dist/gate.js +122 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/role-auth.d.ts +34 -0
- package/dist/role-auth.js +199 -0
- package/dist/role-auth.js.map +1 -0
- package/dist/safe-equal.d.ts +2 -0
- package/dist/safe-equal.js +15 -0
- package/dist/safe-equal.js.map +1 -0
- package/dist/same-site.d.ts +15 -0
- package/dist/same-site.js +57 -0
- package/dist/same-site.js.map +1 -0
- package/dist/scope.d.ts +53 -0
- package/dist/scope.js +50 -0
- package/dist/scope.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authenticated subject. The free tier only uses `roles`; the `permissions`/`orgId` fields are
|
|
3
|
+
* ALREADY reserved for EE (fine-grained RBAC + multi-organization) → the schema won't break later.
|
|
4
|
+
*/
|
|
5
|
+
export interface Principal {
|
|
6
|
+
id?: string;
|
|
7
|
+
/**
|
|
8
|
+
* WHICH CREDENTIAL spoke, never WHO — the two are different questions and this field exists because
|
|
9
|
+
* one field could not answer both.
|
|
10
|
+
*
|
|
11
|
+
* `id` is a SUBJECT: it answers "whose data is this", and `resolveResourceId`
|
|
12
|
+
* (packages/server/src/index.ts) hands it straight to the memory layer as `ThreadRecord.resourceId`,
|
|
13
|
+
* overriding any subject the request named. `credentialId` is a BUDGET KEY: it answers "who is
|
|
14
|
+
* spending", and is safe to synthesize precisely because nothing reads it as an owner.
|
|
15
|
+
*
|
|
16
|
+
* Measured, which is why the separation is enforced rather than advised: filling `id` with a token
|
|
17
|
+
* fingerprint made `resolveResourceId` return that fingerprint for a caller that had explicitly sent
|
|
18
|
+
* `resourceId: 'user-42'`, collapsing `user-42` and `user-99` into ONE bucket with no error — the
|
|
19
|
+
* shared-bucket regression that function's own comment records as measured and fixed, and a silent
|
|
20
|
+
* drop where its stated rule is "an INVALID value is a 400, not a silent drop".
|
|
21
|
+
*
|
|
22
|
+
* So: rate limiting, admission control and quota accounting may key on this. Memory scoping,
|
|
23
|
+
* ownership and access-control decisions MUST NOT — for those, an absent `id` means the deployment
|
|
24
|
+
* genuinely has no per-caller identity, and that absence is load-bearing information.
|
|
25
|
+
*/
|
|
26
|
+
credentialId?: string;
|
|
27
|
+
roles: string[];
|
|
28
|
+
/** The field name identifying an organization — auth-ee/server/studio and stored records use this name. */
|
|
29
|
+
orgId?: string;
|
|
30
|
+
permissions?: string[];
|
|
31
|
+
[k: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
/** Description of what's being accessed — the provider can decide based on path/method/action/resource. */
|
|
34
|
+
export interface AuthContext {
|
|
35
|
+
path: string;
|
|
36
|
+
method: string;
|
|
37
|
+
action: 'read' | 'write';
|
|
38
|
+
resource?: string;
|
|
39
|
+
/**
|
|
40
|
+
* EE fine-grained permission (e.g. 'agents:run', 'users:write'). Set by the gate's `allowP`. When
|
|
41
|
+
* present, an RBAC provider matches THIS exact permission instead of deriving one from resource/action
|
|
42
|
+
* (see @gnldev/auth-ee rbac.ts `requiredPermission`). A free/read-write provider ignores it and falls back
|
|
43
|
+
* to `action` (the gate already reduces the permission to read/write) → coarse but backward-compatible.
|
|
44
|
+
*/
|
|
45
|
+
permission?: string;
|
|
46
|
+
}
|
|
47
|
+
/** The authorization decision. A denial carries `status` (401 identity / 403 authorization) + an optional reason. */
|
|
48
|
+
export type Decision = {
|
|
49
|
+
allow: true;
|
|
50
|
+
} | {
|
|
51
|
+
allow: false;
|
|
52
|
+
status?: 401 | 403;
|
|
53
|
+
reason?: string;
|
|
54
|
+
};
|
|
55
|
+
/** Capabilities declared by the provider → studio `/capabilities` → opens premium UI surfaces. */
|
|
56
|
+
export interface AuthCapabilities {
|
|
57
|
+
sso: boolean;
|
|
58
|
+
rbac: boolean;
|
|
59
|
+
audit: boolean;
|
|
60
|
+
multiOrganization: boolean;
|
|
61
|
+
users: boolean;
|
|
62
|
+
/** EE: license plan name ('pro'/'enterprise'/'dev') — UI badge. Not present in the free tier. */
|
|
63
|
+
plan?: string;
|
|
64
|
+
/** EE: license expiry (epoch ms) — UI expiry warning. Not present when unlimited/free. */
|
|
65
|
+
licenseExp?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Auth provider contract. The host (server/studio) calls `authenticate` first, then `authorize`.
|
|
69
|
+
* `capabilities` is optional (the free tier declares all of them false).
|
|
70
|
+
*/
|
|
71
|
+
export interface AuthProvider {
|
|
72
|
+
authenticate(req: Request): Promise<Principal | null> | Principal | null;
|
|
73
|
+
authorize(principal: Principal | null, req: Request, ctx: AuthContext): Promise<Decision> | Decision;
|
|
74
|
+
capabilities?(): Partial<AuthCapabilities>;
|
|
75
|
+
/**
|
|
76
|
+
* `false` declares that this provider has NO principal model — `authenticate()` returns null for
|
|
77
|
+
* every request, by construction, not because a token was missing. Hosts that scope organizations
|
|
78
|
+
* by identity reject that combination instead of letting a header pick the scope. Omit it (the
|
|
79
|
+
* normal case): a provider that can authenticate needs to say nothing. See `bindsIdentity()`.
|
|
80
|
+
*/
|
|
81
|
+
bindsIdentity?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Credentials for a single role: bearer token and/or basic user+pass. If `orgId` is given, the
|
|
85
|
+
* identity is BOUND to that organization: hosts (server/studio) derive the organization scope from
|
|
86
|
+
* the principal instead of the header; a different `x-gnl-org` request gets a 403 (organization
|
|
87
|
+
* isolation is enforced by identity, it cannot be spoofed).
|
|
88
|
+
*/
|
|
89
|
+
export type Cred = {
|
|
90
|
+
token?: string;
|
|
91
|
+
user?: string;
|
|
92
|
+
pass?: string;
|
|
93
|
+
orgId?: string;
|
|
94
|
+
/**
|
|
95
|
+
* EXPLICIT platform-admin grant (scope: 'platform') — see @gnldev/auth scope.ts. Injects the reserved
|
|
96
|
+
* `platform-admin` role into the principal. Use it to bootstrap a statically-configured root admin
|
|
97
|
+
* that the strict (EE multi-org) model must recognise as cross-org; WITHOUT it, an unbound identity
|
|
98
|
+
* is denied under the strict model (fail-closed). Harmless in the free tier (the role is inert there).
|
|
99
|
+
*/
|
|
100
|
+
platformAdmin?: boolean;
|
|
101
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,mGAAmG","sourcesContent":["// @gnldev/auth — stable auth contract. The free core defines this; @gnldev/auth-ee (paid) implements the\n// same interface → premium (RBAC/SSO/multi-organization/audit) plugs in without touching the core.\n\n/**\n * The authenticated subject. The free tier only uses `roles`; the `permissions`/`orgId` fields are\n * ALREADY reserved for EE (fine-grained RBAC + multi-organization) → the schema won't break later.\n */\nexport interface Principal {\n id?: string;\n /**\n * WHICH CREDENTIAL spoke, never WHO — the two are different questions and this field exists because\n * one field could not answer both.\n *\n * `id` is a SUBJECT: it answers \"whose data is this\", and `resolveResourceId`\n * (packages/server/src/index.ts) hands it straight to the memory layer as `ThreadRecord.resourceId`,\n * overriding any subject the request named. `credentialId` is a BUDGET KEY: it answers \"who is\n * spending\", and is safe to synthesize precisely because nothing reads it as an owner.\n *\n * Measured, which is why the separation is enforced rather than advised: filling `id` with a token\n * fingerprint made `resolveResourceId` return that fingerprint for a caller that had explicitly sent\n * `resourceId: 'user-42'`, collapsing `user-42` and `user-99` into ONE bucket with no error — the\n * shared-bucket regression that function's own comment records as measured and fixed, and a silent\n * drop where its stated rule is \"an INVALID value is a 400, not a silent drop\".\n *\n * So: rate limiting, admission control and quota accounting may key on this. Memory scoping,\n * ownership and access-control decisions MUST NOT — for those, an absent `id` means the deployment\n * genuinely has no per-caller identity, and that absence is load-bearing information.\n */\n credentialId?: string;\n roles: string[];\n /** The field name identifying an organization — auth-ee/server/studio and stored records use this name. */\n orgId?: string;\n permissions?: string[];\n [k: string]: unknown;\n}\n\n/** Description of what's being accessed — the provider can decide based on path/method/action/resource. */\nexport interface AuthContext {\n path: string;\n method: string;\n action: 'read' | 'write';\n resource?: string;\n /**\n * EE fine-grained permission (e.g. 'agents:run', 'users:write'). Set by the gate's `allowP`. When\n * present, an RBAC provider matches THIS exact permission instead of deriving one from resource/action\n * (see @gnldev/auth-ee rbac.ts `requiredPermission`). A free/read-write provider ignores it and falls back\n * to `action` (the gate already reduces the permission to read/write) → coarse but backward-compatible.\n */\n permission?: string;\n}\n\n/** The authorization decision. A denial carries `status` (401 identity / 403 authorization) + an optional reason. */\nexport type Decision = { allow: true } | { allow: false; status?: 401 | 403; reason?: string };\n\n/** Capabilities declared by the provider → studio `/capabilities` → opens premium UI surfaces. */\nexport interface AuthCapabilities {\n sso: boolean;\n rbac: boolean;\n audit: boolean;\n multiOrganization: boolean;\n users: boolean;\n /** EE: license plan name ('pro'/'enterprise'/'dev') — UI badge. Not present in the free tier. */\n plan?: string;\n /** EE: license expiry (epoch ms) — UI expiry warning. Not present when unlimited/free. */\n licenseExp?: number;\n}\n\n/**\n * Auth provider contract. The host (server/studio) calls `authenticate` first, then `authorize`.\n * `capabilities` is optional (the free tier declares all of them false).\n */\nexport interface AuthProvider {\n authenticate(req: Request): Promise<Principal | null> | Principal | null;\n authorize(principal: Principal | null, req: Request, ctx: AuthContext): Promise<Decision> | Decision;\n capabilities?(): Partial<AuthCapabilities>;\n /**\n * `false` declares that this provider has NO principal model — `authenticate()` returns null for\n * every request, by construction, not because a token was missing. Hosts that scope organizations\n * by identity reject that combination instead of letting a header pick the scope. Omit it (the\n * normal case): a provider that can authenticate needs to say nothing. See `bindsIdentity()`.\n */\n bindsIdentity?: boolean;\n}\n\n/**\n * Credentials for a single role: bearer token and/or basic user+pass. If `orgId` is given, the\n * identity is BOUND to that organization: hosts (server/studio) derive the organization scope from\n * the principal instead of the header; a different `x-gnl-org` request gets a 403 (organization\n * isolation is enforced by identity, it cannot be spoofed).\n */\nexport type Cred = {\n token?: string;\n user?: string;\n pass?: string;\n orgId?: string;\n /**\n * EXPLICIT platform-admin grant (scope: 'platform') — see @gnldev/auth scope.ts. Injects the reserved\n * `platform-admin` role into the principal. Use it to bootstrap a statically-configured root admin\n * that the strict (EE multi-org) model must recognise as cross-org; WITHOUT it, an unbound identity\n * is denied under the strict model (fail-closed). Harmless in the free tier (the role is inert there).\n */\n platformAdmin?: boolean;\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gnldev/auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22.13.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "Auth contract (AuthProvider) + free role-based default (bearer/basic, read/write). Open-core seam: @gnldev/auth-ee plugs into the same interface.",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"ai",
|
|
11
|
+
"agent",
|
|
12
|
+
"llm",
|
|
13
|
+
"typescript",
|
|
14
|
+
"ai-sdk",
|
|
15
|
+
"durable",
|
|
16
|
+
"exactly-once",
|
|
17
|
+
"authentication",
|
|
18
|
+
"rbac",
|
|
19
|
+
"api-keys"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"hono": "^4.6.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"hono": "^4.13.2"
|
|
39
|
+
},
|
|
40
|
+
"author": "Karaca Yılmaz (https://gnl.dev)",
|
|
41
|
+
"homepage": "https://gnl.dev",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/Karaca7/gnldev/issues"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/Karaca7/gnldev.git",
|
|
48
|
+
"directory": "packages/auth"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.json",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
|
+
"test": "vitest run"
|
|
57
|
+
}
|
|
58
|
+
}
|