@hilbras/keystone 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth.d.ts +2 -0
- package/dist/plugins/auth.d.ts.map +1 -1
- package/dist/plugins/auth.js +21 -7
- package/dist/plugins/auth.js.map +1 -1
- package/dist/plugins/machinePrincipal.d.ts +28 -0
- package/dist/plugins/machinePrincipal.d.ts.map +1 -0
- package/dist/plugins/machinePrincipal.js +45 -0
- package/dist/plugins/machinePrincipal.js.map +1 -0
- package/dist/routes/apiKeys.d.ts.map +1 -1
- package/dist/routes/apiKeys.js +23 -5
- package/dist/routes/apiKeys.js.map +1 -1
- package/dist/routes/federation.d.ts.map +1 -1
- package/dist/routes/federation.js +8 -2
- package/dist/routes/federation.js.map +1 -1
- package/dist/routes/oauth2.d.ts.map +1 -1
- package/dist/routes/oauth2.js +8 -2
- package/dist/routes/oauth2.js.map +1 -1
- package/dist/routes/profile.js +2 -2
- package/dist/routes/profile.js.map +1 -1
- package/dist/routes/serviceAccounts.d.ts.map +1 -1
- package/dist/routes/serviceAccounts.js +17 -1
- package/dist/routes/serviceAccounts.js.map +1 -1
- package/dist/routes/sessions.js +3 -3
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/smsOtp.d.ts.map +1 -1
- package/dist/routes/smsOtp.js +6 -0
- package/dist/routes/smsOtp.js.map +1 -1
- package/dist/routes/totp.d.ts.map +1 -1
- package/dist/routes/totp.js +20 -5
- package/dist/routes/totp.js.map +1 -1
- package/dist/routes/webauthn.d.ts.map +1 -1
- package/dist/routes/webauthn.js +8 -2
- package/dist/routes/webauthn.js.map +1 -1
- package/dist/services/scopes.d.ts +113 -0
- package/dist/services/scopes.d.ts.map +1 -0
- package/dist/services/scopes.js +138 -0
- package/dist/services/scopes.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key scope registry.
|
|
3
|
+
*
|
|
4
|
+
* API key scopes used to be decorative. `requireScopes` existed and was
|
|
5
|
+
* decorated onto the Fastify instance, but no route referenced it, so a key
|
|
6
|
+
* issued with the default `["api:read"]` carried exactly the same authority as
|
|
7
|
+
* the user who created it. Scopes were also free-form: a caller could request
|
|
8
|
+
* any string at key creation, including the literal `"service_account"` that
|
|
9
|
+
* `requireScopes` treated as a wildcard.
|
|
10
|
+
*
|
|
11
|
+
* A registry fixes both halves. A scope that is not defined here cannot be
|
|
12
|
+
* granted, so there is no string an attacker can invent; and a route that needs a
|
|
13
|
+
* scope names it from this list, so enforcement cannot silently drift out of
|
|
14
|
+
* date the way an unreferenced decorator did.
|
|
15
|
+
*/
|
|
16
|
+
/** Every scope Keystone recognises. Order is the order shown to operators. */
|
|
17
|
+
export const API_KEY_SCOPES = {
|
|
18
|
+
/** Read the caller's own profile, sessions, and keys. */
|
|
19
|
+
"profile:read": "Read your own profile and identity",
|
|
20
|
+
"profile:write": "Update your own profile",
|
|
21
|
+
/** Read sessions belonging to the caller. */
|
|
22
|
+
"sessions:read": "List your own sessions",
|
|
23
|
+
"sessions:revoke": "Revoke your own sessions",
|
|
24
|
+
/** Manage the caller's own API keys. */
|
|
25
|
+
"api_keys:read": "List your own API keys",
|
|
26
|
+
"api_keys:revoke": "Revoke your own API keys",
|
|
27
|
+
/** Read organization members and applications. */
|
|
28
|
+
"organizations:read": "Read organization membership and applications",
|
|
29
|
+
/** Manage organization members, applications, and roles. */
|
|
30
|
+
"organizations:write": "Manage organization membership and applications",
|
|
31
|
+
/** Read and manage webhooks. */
|
|
32
|
+
"webhooks:manage": "Create, update, and delete webhooks",
|
|
33
|
+
/** Read and manage workflows. */
|
|
34
|
+
"workflows:read": "Read workflows and their runs",
|
|
35
|
+
"workflows:write": "Create and update workflows",
|
|
36
|
+
/** Second-factor management. Never appropriate for a machine credential. */
|
|
37
|
+
"mfa:manage": "Enroll, verify, and disable TOTP and backup codes",
|
|
38
|
+
};
|
|
39
|
+
const ALL_SCOPES = Object.keys(API_KEY_SCOPES);
|
|
40
|
+
export function isApiKeyScope(value) {
|
|
41
|
+
return Object.prototype.hasOwnProperty.call(API_KEY_SCOPES, value);
|
|
42
|
+
}
|
|
43
|
+
export function knownScopes() {
|
|
44
|
+
return [...ALL_SCOPES];
|
|
45
|
+
}
|
|
46
|
+
/** Human-readable description of a scope, for API responses and dashboards. */
|
|
47
|
+
export function describeScope(scope) {
|
|
48
|
+
return isApiKeyScope(scope) ? API_KEY_SCOPES[scope] : undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Scopes granted to a service account by default.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately excludes anything interactive: a machine credential has no user
|
|
54
|
+
* present to complete a second factor, no real profile, and no sessions worth
|
|
55
|
+
* listing. A service account that needs more is granted it explicitly.
|
|
56
|
+
*/
|
|
57
|
+
export const SERVICE_ACCOUNT_DEFAULT_SCOPES = ["organizations:read"];
|
|
58
|
+
/**
|
|
59
|
+
* Scopes a new personal API key receives when the caller names none.
|
|
60
|
+
*
|
|
61
|
+
* Read-only and confined to the caller's own resources. The previous default,
|
|
62
|
+
* `["api:read"]`, was not in the registry at all — which is a fair indicator that
|
|
63
|
+
* nothing was checking it.
|
|
64
|
+
*/
|
|
65
|
+
export const PERSONAL_KEY_DEFAULT_SCOPES = ["profile:read", "sessions:read", "api_keys:read"];
|
|
66
|
+
/**
|
|
67
|
+
* Scopes that may never be granted to a machine credential.
|
|
68
|
+
*
|
|
69
|
+
* `mfa:manage` because a second factor is enrolled by a person holding an
|
|
70
|
+
* authenticator; `MfaRequiredError` is raised when a token is minted for a user
|
|
71
|
+
* with TOTP enabled and no recorded factor, and a service account has no
|
|
72
|
+
* authenticator to record.
|
|
73
|
+
*
|
|
74
|
+
* `profile:*` because a service account's "profile" is a synthesized object with
|
|
75
|
+
* an id of `sa:<uuid>` that matches no user row. Reading it returns nothing real,
|
|
76
|
+
* and writing it would update zero rows -- so the scope is meaningless for a
|
|
77
|
+
* machine and misleading in an audit log that shows what a key was used for.
|
|
78
|
+
*/
|
|
79
|
+
export const HUMAN_ONLY_SCOPES = [
|
|
80
|
+
"mfa:manage",
|
|
81
|
+
"profile:read",
|
|
82
|
+
"profile:write",
|
|
83
|
+
];
|
|
84
|
+
/**
|
|
85
|
+
* Validate a requested scope set.
|
|
86
|
+
*
|
|
87
|
+
* Unknown scopes are refused rather than dropped. Silently discarding a scope a
|
|
88
|
+
* caller asked for hides both a typo and an attempt to smuggle an unrecognised
|
|
89
|
+
* privilege past a review, and it leaves the caller believing they hold
|
|
90
|
+
* something they do not.
|
|
91
|
+
*/
|
|
92
|
+
export function validateScopes(requested, options = { principal: "user" }) {
|
|
93
|
+
const wanted = requested && requested.length > 0
|
|
94
|
+
? requested
|
|
95
|
+
: (options.fallback ??
|
|
96
|
+
(options.principal === "service_account" ? SERVICE_ACCOUNT_DEFAULT_SCOPES : PERSONAL_KEY_DEFAULT_SCOPES));
|
|
97
|
+
const unknown = wanted.filter((scope) => !isApiKeyScope(scope));
|
|
98
|
+
const known = wanted.filter(isApiKeyScope);
|
|
99
|
+
const forbidden = options.principal === "service_account"
|
|
100
|
+
? known.filter((scope) => HUMAN_ONLY_SCOPES.includes(scope))
|
|
101
|
+
: [];
|
|
102
|
+
const allowed = known.filter((scope) => !forbidden.includes(scope));
|
|
103
|
+
return {
|
|
104
|
+
ok: unknown.length === 0 && forbidden.length === 0,
|
|
105
|
+
unknown,
|
|
106
|
+
forbiddenForServiceAccount: forbidden,
|
|
107
|
+
// Registry order, so two equivalent requests produce identical rows.
|
|
108
|
+
scopes: ALL_SCOPES.filter((scope) => allowed.includes(scope)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Narrow a key's stored scopes to what the caller is actually entitled to.
|
|
113
|
+
*
|
|
114
|
+
* A key never gains authority from its own record. The effective set is what the
|
|
115
|
+
* key holds *intersected with* what its principal could do directly, so revoking
|
|
116
|
+
* a role or removing a membership takes effect on the key too rather than
|
|
117
|
+
* leaving a stale grant behind.
|
|
118
|
+
*/
|
|
119
|
+
export function intersectScopes(granted, principalAllows) {
|
|
120
|
+
const held = new Set((granted ?? []).filter(isApiKeyScope));
|
|
121
|
+
const allowed = new Set(principalAllows.filter(isApiKeyScope));
|
|
122
|
+
return ALL_SCOPES.filter((scope) => held.has(scope) && allowed.has(scope));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Grant check used by `requireScopes`.
|
|
126
|
+
*
|
|
127
|
+
* Explicit allow-list membership only. The previous implementation also accepted
|
|
128
|
+
* a key whose scopes merely *contained* the string `"service_account"`, which is
|
|
129
|
+
* a bypass by construction: that string was client-suppliable at key creation, so
|
|
130
|
+
* any key created with `scopes: ["service_account"]` satisfied every check.
|
|
131
|
+
*/
|
|
132
|
+
export function hasScopes(granted, required) {
|
|
133
|
+
if (required.length === 0)
|
|
134
|
+
return true;
|
|
135
|
+
const held = new Set(granted ?? []);
|
|
136
|
+
return required.every((scope) => held.has(scope));
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/services/scopes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED