@nocobase/app-plugin-api-keys 0.0.0-stage → 1.0.0-beta.13
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 +418 -0
- package/README.md +165 -2
- package/dist/client/api-keys-client.d.ts +19 -0
- package/dist/client/api-keys-client.d.ts.map +1 -0
- package/dist/client/api-keys-client.js +7 -0
- package/dist/client/api-keys-client.js.map +1 -0
- package/dist/client/components/page-container.d.ts +4 -0
- package/dist/client/components/page-container.d.ts.map +1 -0
- package/dist/client/components/page-container.js +6 -0
- package/dist/client/components/page-container.js.map +1 -0
- package/dist/client/components/page-header.d.ts +8 -0
- package/dist/client/components/page-header.d.ts.map +1 -0
- package/dist/client/components/page-header.js +5 -0
- package/dist/client/components/page-header.js.map +1 -0
- package/dist/client/components/ui/alert-dialog.d.ts +19 -0
- package/dist/client/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/client/components/ui/alert-dialog.js +42 -0
- package/dist/client/components/ui/alert-dialog.js.map +1 -0
- package/dist/client/components/ui/badge.d.ts +6 -0
- package/dist/client/components/ui/badge.d.ts.map +1 -0
- package/dist/client/components/ui/badge.js +8 -0
- package/dist/client/components/ui/badge.js.map +1 -0
- package/dist/client/components/ui/button.d.ts +8 -0
- package/dist/client/components/ui/button.d.ts.map +1 -0
- package/dist/client/components/ui/button.js +27 -0
- package/dist/client/components/ui/button.js.map +1 -0
- package/dist/client/components/ui/dialog.d.ts +9 -0
- package/dist/client/components/ui/dialog.d.ts.map +1 -0
- package/dist/client/components/ui/dialog.js +29 -0
- package/dist/client/components/ui/dialog.js.map +1 -0
- package/dist/client/components/ui/input.d.ts +4 -0
- package/dist/client/components/ui/input.d.ts.map +1 -0
- package/dist/client/components/ui/input.js +8 -0
- package/dist/client/components/ui/input.js.map +1 -0
- package/dist/client/components/ui/label.d.ts +3 -0
- package/dist/client/components/ui/label.d.ts.map +1 -0
- package/dist/client/components/ui/label.js +6 -0
- package/dist/client/components/ui/label.js.map +1 -0
- package/dist/client/components/ui/select.d.ts +8 -0
- package/dist/client/components/ui/select.d.ts.map +1 -0
- package/dist/client/components/ui/select.js +19 -0
- package/dist/client/components/ui/select.js.map +1 -0
- package/dist/client/components/ui/spinner.d.ts +4 -0
- package/dist/client/components/ui/spinner.d.ts.map +1 -0
- package/dist/client/components/ui/spinner.js +11 -0
- package/dist/client/components/ui/spinner.js.map +1 -0
- package/dist/client/components/ui/table.d.ts +8 -0
- package/dist/client/components/ui/table.d.ts.map +1 -0
- package/dist/client/components/ui/table.js +21 -0
- package/dist/client/components/ui/table.js.map +1 -0
- package/dist/client/expiry.d.ts +23 -0
- package/dist/client/expiry.d.ts.map +1 -0
- package/dist/client/expiry.js +40 -0
- package/dist/client/expiry.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/lib/utils.d.ts +2 -0
- package/dist/client/lib/utils.d.ts.map +1 -0
- package/dist/client/lib/utils.js +2 -0
- package/dist/client/lib/utils.js.map +1 -0
- package/dist/client/locales/en-US.d.ts +71 -0
- package/dist/client/locales/en-US.d.ts.map +1 -0
- package/dist/client/locales/en-US.js +62 -0
- package/dist/client/locales/en-US.js.map +1 -0
- package/dist/client/locales/index.d.ts +4 -0
- package/dist/client/locales/index.d.ts.map +1 -0
- package/dist/client/locales/index.js +7 -0
- package/dist/client/locales/index.js.map +1 -0
- package/dist/client/locales/zh-CN.d.ts +4 -0
- package/dist/client/locales/zh-CN.d.ts.map +1 -0
- package/dist/client/locales/zh-CN.js +62 -0
- package/dist/client/locales/zh-CN.js.map +1 -0
- package/dist/client/pages/api-keys-page.d.ts +3 -0
- package/dist/client/pages/api-keys-page.d.ts.map +1 -0
- package/dist/client/pages/api-keys-page.js +161 -0
- package/dist/client/pages/api-keys-page.js.map +1 -0
- package/dist/client/plugin.d.ts +9 -0
- package/dist/client/plugin.d.ts.map +1 -0
- package/dist/client/plugin.js +10 -0
- package/dist/client/plugin.js.map +1 -0
- package/dist/client/routes.d.ts +22 -0
- package/dist/client/routes.d.ts.map +1 -0
- package/dist/client/routes.js +42 -0
- package/dist/client/routes.js.map +1 -0
- package/dist/database/migrations/.manifest.json +9 -0
- package/dist/database/migrations/202609150001_create_api_key_table.js +58 -0
- package/dist/database/migrations/202609150001_create_api_key_table.js.map +1 -0
- package/dist/server/api-docs.d.ts +24 -0
- package/dist/server/api-docs.d.ts.map +1 -0
- package/dist/server/api-docs.js +73 -0
- package/dist/server/api-docs.js.map +1 -0
- package/dist/server/api-keys.d.ts +52 -0
- package/dist/server/api-keys.d.ts.map +1 -0
- package/dist/server/api-keys.js +199 -0
- package/dist/server/api-keys.js.map +1 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +22 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/key-sessions.d.ts +35 -0
- package/dist/server/key-sessions.d.ts.map +1 -0
- package/dist/server/key-sessions.js +50 -0
- package/dist/server/key-sessions.js.map +1 -0
- package/dist/server/plugin.d.ts +4 -0
- package/dist/server/plugin.d.ts.map +1 -0
- package/dist/server/plugin.js +15 -0
- package/dist/server/plugin.js.map +1 -0
- package/dist/server/providers/index.d.ts +19 -0
- package/dist/server/providers/index.d.ts.map +1 -0
- package/dist/server/providers/index.js +72 -0
- package/dist/server/providers/index.js.map +1 -0
- package/dist/server/routes/errors.d.ts +8 -0
- package/dist/server/routes/errors.d.ts.map +1 -0
- package/dist/server/routes/errors.js +48 -0
- package/dist/server/routes/errors.js.map +1 -0
- package/dist/server/routes/index.d.ts +10 -0
- package/dist/server/routes/index.d.ts.map +1 -0
- package/dist/server/routes/index.js +177 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/schemas.d.ts +25 -0
- package/dist/server/routes/schemas.d.ts.map +1 -0
- package/dist/server/routes/schemas.js +127 -0
- package/dist/server/routes/schemas.js.map +1 -0
- package/dist/server/scoped-keys.d.ts +116 -0
- package/dist/server/scoped-keys.d.ts.map +1 -0
- package/dist/server/scoped-keys.js +451 -0
- package/dist/server/scoped-keys.js.map +1 -0
- package/dist/server/scopes.d.ts +70 -0
- package/dist/server/scopes.d.ts.map +1 -0
- package/dist/server/scopes.js +165 -0
- package/dist/server/scopes.js.map +1 -0
- package/dist/server/service.d.ts +28 -0
- package/dist/server/service.d.ts.map +1 -0
- package/dist/server/service.js +69 -0
- package/dist/server/service.js.map +1 -0
- package/dist/server/tokens.d.ts +6 -0
- package/dist/server/tokens.d.ts.map +1 -0
- package/dist/server/tokens.js +5 -0
- package/dist/server/tokens.js.map +1 -0
- package/dist/shared/scopes.d.ts +160 -0
- package/dist/shared/scopes.d.ts.map +1 -0
- package/dist/shared/scopes.js +83 -0
- package/dist/shared/scopes.js.map +1 -0
- package/package.json +91 -3
- package/skills/nocobase-app-plugin-api-keys/SKILL.md +166 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key-scope registry: the permission groups and presets an application assembles from its plugins, how each
|
|
3
|
+
* `AccessRef` maps to an authorization resource and action, and how a stored scope becomes the `KeyScope` the
|
|
4
|
+
* authorization core enforces. Plugins never import this; the application hands their declarations to it.
|
|
5
|
+
*/
|
|
6
|
+
import type { AuthorizationIdentity, KeyScope, ResourceRef } from '@nocobase/authorization/core';
|
|
7
|
+
import { type ServiceToken } from '@nocobase/service-provider';
|
|
8
|
+
import { type AccessRef, type KeyScopeGroupDeclaration, type KeyScopeInput, type KeyScopeLevel, type KeyScopeObject, type KeyScopePresetDeclaration } from '../shared/scopes.js';
|
|
9
|
+
/** Lists the records a group may be limited to: only those the identity may see in that business. */
|
|
10
|
+
export interface KeyScopeObjectSource {
|
|
11
|
+
list(input: {
|
|
12
|
+
readonly identity: AuthorizationIdentity;
|
|
13
|
+
readonly search?: string;
|
|
14
|
+
readonly ids?: readonly string[];
|
|
15
|
+
}): Promise<readonly KeyScopeObject[]>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What an `AccessRef` is checked as: one action of a resource, or several where holding any of them is holding the ref
|
|
19
|
+
* (a business action granted once per level, `edit.related` and `edit.all`); a scope covering the ref allows every one.
|
|
20
|
+
* Null when the application does not offer it.
|
|
21
|
+
*/
|
|
22
|
+
export type AccessMapping = (ref: AccessRef) => {
|
|
23
|
+
readonly resource: ResourceRef;
|
|
24
|
+
readonly action: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly resource: ResourceRef;
|
|
27
|
+
readonly actions: readonly string[];
|
|
28
|
+
} | null;
|
|
29
|
+
/** A mapped `AccessRef`: the actions of a resource it stands for, any one of which is holding it. */
|
|
30
|
+
export interface MappedAccess {
|
|
31
|
+
readonly resource: ResourceRef;
|
|
32
|
+
readonly actions: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
/** A refused scope: 400 with a stable code. */
|
|
35
|
+
export declare class ApiKeyScopeError extends Error {
|
|
36
|
+
readonly code: 'INVALID_SCOPE' | 'EMPTY_SCOPE' | 'UNKNOWN_SCOPE_GROUP' | 'SCOPE_LEVEL_NOT_OFFERED' | 'SCOPE_OBJECTS_NOT_OFFERED' | 'UNKNOWN_SCOPE_OBJECT';
|
|
37
|
+
constructor(code: 'INVALID_SCOPE' | 'EMPTY_SCOPE' | 'UNKNOWN_SCOPE_GROUP' | 'SCOPE_LEVEL_NOT_OFFERED' | 'SCOPE_OBJECTS_NOT_OFFERED' | 'UNKNOWN_SCOPE_OBJECT', message: string);
|
|
38
|
+
}
|
|
39
|
+
export interface ApiKeyScopes {
|
|
40
|
+
readonly groups: {
|
|
41
|
+
/** Registers a group, with the source of its records when it offers object selection. Returns its removal. */
|
|
42
|
+
add(group: KeyScopeGroupDeclaration, objects?: KeyScopeObjectSource): () => void;
|
|
43
|
+
list(): readonly KeyScopeGroupDeclaration[];
|
|
44
|
+
get(id: string): KeyScopeGroupDeclaration | undefined;
|
|
45
|
+
/** The record source of a group that offers object selection. */
|
|
46
|
+
objects(id: string): KeyScopeObjectSource | undefined;
|
|
47
|
+
};
|
|
48
|
+
readonly presets: {
|
|
49
|
+
add(preset: KeyScopePresetDeclaration): () => void;
|
|
50
|
+
list(): readonly KeyScopePresetDeclaration[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Sets how an `AccessRef` is checked. The default maps a page to `page:<id>/access` and a settings action to
|
|
54
|
+
* `settings:<id>/<action>`, and offers no business action, whose resource type only the application knows.
|
|
55
|
+
*/
|
|
56
|
+
mapAccess(mapping: AccessMapping): void;
|
|
57
|
+
/** The resource actions a level of a group covers, mapped; refs the application does not offer are left out. */
|
|
58
|
+
accessOf(group: KeyScopeGroupDeclaration, level: KeyScopeLevel, options?: {
|
|
59
|
+
readonly allObjects?: boolean;
|
|
60
|
+
}): MappedAccess[];
|
|
61
|
+
/** Checks a scope against the registered groups; throws `ApiKeyScopeError`. Record ids are checked by the caller. */
|
|
62
|
+
validate(input: unknown): KeyScopeInput;
|
|
63
|
+
/** The `KeyScope` of a stored scope. Groups no longer registered grant nothing, so a removed group only narrows. */
|
|
64
|
+
compile(keyId: string, scope: KeyScopeInput): KeyScope;
|
|
65
|
+
/** The `KeyScope` of a key bounded only by its owner (a service account's unscoped key): it narrows nothing. */
|
|
66
|
+
unbounded(keyId: string): KeyScope;
|
|
67
|
+
}
|
|
68
|
+
export declare const apiKeyScopesToken: ServiceToken<ApiKeyScopes>;
|
|
69
|
+
export declare function createApiKeyScopes(): ApiKeyScopes;
|
|
70
|
+
//# sourceMappingURL=scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../server/scopes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACV,qBAAqB,EACrB,QAAQ,EACR,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,wBAAwB,EAE7B,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC/B,MAAM,qBAAqB,CAAC;AAE7B,qGAAqG;AACrG,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,KAAK,EAAE;QACV,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;QACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAClC,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,SAAS,KAEZ;IAAE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACvE,IAAI,CAAC;AAET,qGAAqG;AACrG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,+CAA+C;AAC/C,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,IAAI,EACT,eAAe,GACf,aAAa,GACb,qBAAqB,GACrB,yBAAyB,GACzB,2BAA2B,GAC3B,sBAAsB;gBANjB,IAAI,EACT,eAAe,GACf,aAAa,GACb,qBAAqB,GACrB,yBAAyB,GACzB,2BAA2B,GAC3B,sBAAsB,EAC1B,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE;QACf,8GAA8G;QAC9G,GAAG,CACD,KAAK,EAAE,wBAAwB,EAC/B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,MAAM,IAAI,CAAC;QACd,IAAI,IAAI,SAAS,wBAAwB,EAAE,CAAC;QAC5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAAC;QACtD,iEAAiE;QACjE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAC;KACvD,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QAChB,GAAG,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,IAAI,CAAC;QACnD,IAAI,IAAI,SAAS,yBAAyB,EAAE,CAAC;KAC9C,CAAC;IACF;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACxC,gHAAgH;IAChH,QAAQ,CACN,KAAK,EAAE,wBAAwB,EAC/B,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1C,YAAY,EAAE,CAAC;IAClB,qHAAqH;IACrH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAAC;IACxC,oHAAoH;IACpH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAAC;IACvD,gHAAgH;IAChH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;CACpC;AAED,eAAO,MAAM,iBAAiB,EAAE,YAAY,CAAC,YAAY,CACiB,CAAC;AAmB3E,wBAAgB,kBAAkB,IAAI,YAAY,CAuLjD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createServiceToken, } from '@nocobase/service-provider';
|
|
2
|
+
import { KEY_SCOPE_LEVELS, levelsUpTo, pickedObjects, } from '../shared/scopes.js';
|
|
3
|
+
/** A refused scope: 400 with a stable code. */
|
|
4
|
+
export class ApiKeyScopeError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = 'ApiKeyScopeError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const apiKeyScopesToken = createServiceToken('@nocobase/app-plugin-api-keys/scopes');
|
|
13
|
+
const defaultMapping = (ref) => {
|
|
14
|
+
if (ref.kind === 'page')
|
|
15
|
+
return { resource: { type: 'page', id: ref.id }, action: 'access' };
|
|
16
|
+
if (ref.kind === 'settings')
|
|
17
|
+
return { resource: { type: 'settings', id: ref.id }, action: ref.action };
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
const refKey = (ref) => ref.kind === 'page'
|
|
21
|
+
? `page:${ref.id}`
|
|
22
|
+
: `${ref.kind}:${ref.id}/${ref.action}`;
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
export function createApiKeyScopes() {
|
|
27
|
+
const groups = new Map();
|
|
28
|
+
const presets = new Map();
|
|
29
|
+
let mapping = defaultMapping;
|
|
30
|
+
const offered = (group) => KEY_SCOPE_LEVELS.filter((level) => group.levels[level] !== undefined);
|
|
31
|
+
const accessOf = (group, level, options = {}) => {
|
|
32
|
+
const unscopedOnly = new Set((group.objects?.allowsUnscoped ?? []).map(refKey));
|
|
33
|
+
const result = [];
|
|
34
|
+
for (const step of levelsUpTo(level))
|
|
35
|
+
for (const ref of group.levels[step] ?? []) {
|
|
36
|
+
if (options.allObjects === false && unscopedOnly.has(refKey(ref)))
|
|
37
|
+
continue;
|
|
38
|
+
const mapped = mapping(ref);
|
|
39
|
+
if (mapped)
|
|
40
|
+
result.push({
|
|
41
|
+
resource: mapped.resource,
|
|
42
|
+
actions: 'actions' in mapped ? mapped.actions : [mapped.action],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
function validateGrant(id, value) {
|
|
48
|
+
const group = groups.get(id)?.group;
|
|
49
|
+
if (!group)
|
|
50
|
+
throw new ApiKeyScopeError('UNKNOWN_SCOPE_GROUP', `${id} is not a permission group.`);
|
|
51
|
+
if (!isRecord(value) || typeof value.level !== 'string')
|
|
52
|
+
throw new ApiKeyScopeError('INVALID_SCOPE', `${id} must be { level, objects? }.`);
|
|
53
|
+
const level = value.level;
|
|
54
|
+
if (!offered(group).includes(level))
|
|
55
|
+
throw new ApiKeyScopeError('SCOPE_LEVEL_NOT_OFFERED', `${id} offers ${offered(group).join(', ')}, not ${String(value.level)}.`);
|
|
56
|
+
const objects = value.objects;
|
|
57
|
+
if (objects === undefined || objects === 'all')
|
|
58
|
+
return { level };
|
|
59
|
+
if (!group.objects)
|
|
60
|
+
throw new ApiKeyScopeError('SCOPE_OBJECTS_NOT_OFFERED', `${id} cannot be limited to some records.`);
|
|
61
|
+
// An empty list reaches none of the records: the group's actions with nothing to act on, such as a key an
|
|
62
|
+
// application recognizes by its identity for records it does not have yet.
|
|
63
|
+
if (!Array.isArray(objects) ||
|
|
64
|
+
objects.some((item) => typeof item !== 'string' || !item.trim()))
|
|
65
|
+
throw new ApiKeyScopeError('INVALID_SCOPE', `${id}.objects must be "all" or a list of ids.`);
|
|
66
|
+
return { level, objects: [...new Set(objects)] };
|
|
67
|
+
}
|
|
68
|
+
function scopeOf(keyId, entries, objects) {
|
|
69
|
+
const key = (resource) => JSON.stringify([resource.type, resource.id]);
|
|
70
|
+
const resources = new Map();
|
|
71
|
+
for (const [entry] of entries) {
|
|
72
|
+
const [type, id] = JSON.parse(entry);
|
|
73
|
+
resources.set(entry, { type, id });
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
keyId,
|
|
77
|
+
allows: (resource, action) => entries.get(key(resource))?.has(action) ?? false,
|
|
78
|
+
objects: (business) => objects.get(business) ?? 'all',
|
|
79
|
+
permissions: [...entries].map(([entry, actions]) => ({
|
|
80
|
+
resource: resources.get(entry),
|
|
81
|
+
actions: [...actions].sort(),
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
groups: {
|
|
87
|
+
add(group, objects) {
|
|
88
|
+
if (groups.has(group.id))
|
|
89
|
+
throw new Error(`Permission group ${group.id} is registered already.`);
|
|
90
|
+
if (group.objects) {
|
|
91
|
+
const business = group.objects.business;
|
|
92
|
+
for (const existing of groups.values())
|
|
93
|
+
if (existing.group.objects?.business === business)
|
|
94
|
+
throw new Error(`Permission groups ${existing.group.id} and ${group.id} both select records of ${business}.`);
|
|
95
|
+
}
|
|
96
|
+
if (offered(group).length === 0)
|
|
97
|
+
throw new Error(`Permission group ${group.id} offers no level.`);
|
|
98
|
+
const entry = { group, ...(objects ? { objects } : {}) };
|
|
99
|
+
groups.set(group.id, entry);
|
|
100
|
+
return () => {
|
|
101
|
+
if (groups.get(group.id) === entry)
|
|
102
|
+
groups.delete(group.id);
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
list: () => [...groups.values()].map((entry) => entry.group),
|
|
106
|
+
get: (id) => groups.get(id)?.group,
|
|
107
|
+
objects: (id) => groups.get(id)?.objects,
|
|
108
|
+
},
|
|
109
|
+
presets: {
|
|
110
|
+
add(preset) {
|
|
111
|
+
if (presets.has(preset.id))
|
|
112
|
+
throw new Error(`Key scope preset ${preset.id} is registered already.`);
|
|
113
|
+
presets.set(preset.id, preset);
|
|
114
|
+
return () => {
|
|
115
|
+
if (presets.get(preset.id) === preset)
|
|
116
|
+
presets.delete(preset.id);
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
list: () => [...presets.values()],
|
|
120
|
+
},
|
|
121
|
+
mapAccess(next) {
|
|
122
|
+
mapping = next;
|
|
123
|
+
},
|
|
124
|
+
accessOf,
|
|
125
|
+
validate(input) {
|
|
126
|
+
if (!isRecord(input) || !isRecord(input.groups))
|
|
127
|
+
throw new ApiKeyScopeError('INVALID_SCOPE', 'A scope is { groups: { <group>: { level, objects? } } }.');
|
|
128
|
+
const result = {};
|
|
129
|
+
for (const [id, value] of Object.entries(input.groups))
|
|
130
|
+
result[id] = validateGrant(id, value);
|
|
131
|
+
if (Object.keys(result).length === 0)
|
|
132
|
+
throw new ApiKeyScopeError('EMPTY_SCOPE', 'A scoped key needs at least one permission.');
|
|
133
|
+
return { groups: result };
|
|
134
|
+
},
|
|
135
|
+
compile(keyId, scope) {
|
|
136
|
+
const entries = new Map();
|
|
137
|
+
const objects = new Map();
|
|
138
|
+
for (const [id, grant] of Object.entries(scope.groups)) {
|
|
139
|
+
const group = groups.get(id)?.group;
|
|
140
|
+
if (!group || !offered(group).includes(grant.level))
|
|
141
|
+
continue;
|
|
142
|
+
const picked = group.objects ? pickedObjects(grant) : undefined;
|
|
143
|
+
if (picked && group.objects)
|
|
144
|
+
objects.set(group.objects.business, picked);
|
|
145
|
+
for (const { resource, actions: mapped } of accessOf(group, grant.level, { allObjects: picked === undefined })) {
|
|
146
|
+
const key = JSON.stringify([resource.type, resource.id]);
|
|
147
|
+
const actions = entries.get(key) ?? new Set();
|
|
148
|
+
for (const action of mapped)
|
|
149
|
+
actions.add(action);
|
|
150
|
+
entries.set(key, actions);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return scopeOf(keyId, entries, objects);
|
|
154
|
+
},
|
|
155
|
+
unbounded(keyId) {
|
|
156
|
+
return {
|
|
157
|
+
keyId,
|
|
158
|
+
allows: () => true,
|
|
159
|
+
objects: () => 'all',
|
|
160
|
+
permissions: null,
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../server/scopes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ApiKey } from '@better-auth/api-key';
|
|
2
|
+
import type { Auth } from '@nocobase/app-plugin-authentication/server';
|
|
3
|
+
import type { DatabaseConnection } from '@nocobase/db';
|
|
4
|
+
export type ServerApiKeySummary = Omit<ApiKey, 'key'>;
|
|
5
|
+
export interface CreateServerApiKeyInput {
|
|
6
|
+
readonly userId: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly expiresIn?: number | null;
|
|
9
|
+
}
|
|
10
|
+
/** Trusted, database-backed operations. Callers must enforce their own authorization. */
|
|
11
|
+
export declare class ApiKeyService {
|
|
12
|
+
private readonly auth;
|
|
13
|
+
private readonly configId;
|
|
14
|
+
constructor(auth: Pick<Auth, 'pluginApi' | 'forConnection'>, configId: string);
|
|
15
|
+
withConnection(connection: DatabaseConnection): ApiKeyService;
|
|
16
|
+
private runtime;
|
|
17
|
+
create(input: CreateServerApiKeyInput): Promise<{
|
|
18
|
+
key: ServerApiKeySummary;
|
|
19
|
+
secret: string;
|
|
20
|
+
}>;
|
|
21
|
+
verify(secret: string): Promise<ServerApiKeySummary | null>;
|
|
22
|
+
get(id: string): Promise<ServerApiKeySummary | null>;
|
|
23
|
+
disable(id: string): Promise<void>;
|
|
24
|
+
remove(id: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/** Remove database-backed user credentials in the caller's deletion transaction. */
|
|
27
|
+
export declare function removeUserApiKeys(connection: DatabaseConnection, userId: string, configIds: readonly string[]): Promise<void>;
|
|
28
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../server/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;AAEvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACtD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,yFAAyF;AACzF,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,CAAC,EAC/C,QAAQ,EAAE,MAAM;IAGnC,cAAc,CAAC,UAAU,EAAE,kBAAkB,GAAG,aAAa;YAO/C,OAAO;IAOf,MAAM,CACV,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC;QAAE,GAAG,EAAE,mBAAmB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAQlD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAQ3D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAOpD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAclC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMxC;AAED,oFAAoF;AACpF,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,kBAAkB,EAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Trusted, database-backed operations. Callers must enforce their own authorization. */
|
|
2
|
+
export class ApiKeyService {
|
|
3
|
+
auth;
|
|
4
|
+
configId;
|
|
5
|
+
constructor(auth, configId) {
|
|
6
|
+
this.auth = auth;
|
|
7
|
+
this.configId = configId;
|
|
8
|
+
}
|
|
9
|
+
withConnection(connection) {
|
|
10
|
+
return new ApiKeyService(this.auth.forConnection(connection), this.configId);
|
|
11
|
+
}
|
|
12
|
+
async runtime() {
|
|
13
|
+
const endpoints = this.auth.pluginApi('api-key');
|
|
14
|
+
// Validate the configuration rather than accepting Better Auth's fallback to default.
|
|
15
|
+
await endpoints.getServerApiKey({ query: { configId: this.configId } });
|
|
16
|
+
return endpoints;
|
|
17
|
+
}
|
|
18
|
+
async create(input) {
|
|
19
|
+
const endpoints = await this.runtime();
|
|
20
|
+
const { key: secret, ...key } = await endpoints.createApiKey({
|
|
21
|
+
body: { ...input, configId: this.configId },
|
|
22
|
+
});
|
|
23
|
+
return { key, secret };
|
|
24
|
+
}
|
|
25
|
+
async verify(secret) {
|
|
26
|
+
const endpoints = await this.runtime();
|
|
27
|
+
const result = await endpoints.verifyApiKey({
|
|
28
|
+
body: { configId: this.configId, key: secret },
|
|
29
|
+
});
|
|
30
|
+
return result.valid ? result.key : null;
|
|
31
|
+
}
|
|
32
|
+
async get(id) {
|
|
33
|
+
const endpoints = await this.runtime();
|
|
34
|
+
return endpoints.getServerApiKey({
|
|
35
|
+
query: { id, configId: this.configId },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async disable(id) {
|
|
39
|
+
const key = await this.get(id);
|
|
40
|
+
if (!key || !key.enabled)
|
|
41
|
+
return;
|
|
42
|
+
const endpoints = await this.runtime();
|
|
43
|
+
await endpoints.updateApiKey({
|
|
44
|
+
body: {
|
|
45
|
+
configId: this.configId,
|
|
46
|
+
keyId: id,
|
|
47
|
+
userId: key.referenceId,
|
|
48
|
+
enabled: false,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async remove(id) {
|
|
53
|
+
const endpoints = await this.runtime();
|
|
54
|
+
await endpoints.deleteServerApiKey({
|
|
55
|
+
body: { keyId: id, configId: this.configId },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Remove database-backed user credentials in the caller's deletion transaction. */
|
|
60
|
+
export async function removeUserApiKeys(connection, userId, configIds) {
|
|
61
|
+
if (!configIds.length)
|
|
62
|
+
return;
|
|
63
|
+
await connection.query
|
|
64
|
+
.deleteFrom('apikey')
|
|
65
|
+
.where('referenceId', '=', userId)
|
|
66
|
+
.where('configId', 'in', [...configIds])
|
|
67
|
+
.execute();
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../server/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ServiceToken } from '@nocobase/service-provider';
|
|
2
|
+
import type { ScopedApiKeys } from './scoped-keys.js';
|
|
3
|
+
export { apiKeyScopesToken } from './scopes.js';
|
|
4
|
+
/** Keys with scopes for any user: what an application builds key pages and service-account keys on. */
|
|
5
|
+
export declare const scopedApiKeysToken: ServiceToken<ScopedApiKeys>;
|
|
6
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../server/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,uGAAuG;AACvG,eAAO,MAAM,kBAAkB,EAAE,YAAY,CAAC,aAAa,CAGxD,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createServiceToken, } from '@nocobase/service-provider';
|
|
2
|
+
export { apiKeyScopesToken } from './scopes.js';
|
|
3
|
+
/** Keys with scopes for any user: what an application builds key pages and service-account keys on. */
|
|
4
|
+
export const scopedApiKeysToken = createServiceToken('@nocobase/app-plugin-api-keys/scoped-keys');
|
|
5
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../server/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAEnB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,uGAAuG;AACvG,MAAM,CAAC,MAAM,kBAAkB,GAC7B,kBAAkB,CAChB,2CAA2C,CAC5C,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key scopes as data: what a plugin declares it offers (permission groups and presets), what a key's scope holds, and
|
|
3
|
+
* how a scope is stored in Better Auth's `permissions` column. Nothing here imports another plugin, so a plugin that
|
|
4
|
+
* contributes a group writes a literal of the same shape without depending on this package.
|
|
5
|
+
*
|
|
6
|
+
* A scope only ever narrows: a key's effective permission is its owner's current permission intersected with the
|
|
7
|
+
* scope. A key without a scope (`permissions = null`) is its owner, exactly as before scopes existed.
|
|
8
|
+
*/
|
|
9
|
+
/** A level of a group; each includes the ones before it. */
|
|
10
|
+
export type KeyScopeLevel = 'read' | 'write' | 'admin';
|
|
11
|
+
export declare const KEY_SCOPE_LEVELS: readonly KeyScopeLevel[];
|
|
12
|
+
/** A title as the authorization plugin stores it: plain text, or an i18n key in a namespace. */
|
|
13
|
+
export type LocalizedText = string | {
|
|
14
|
+
readonly key: string;
|
|
15
|
+
readonly ns: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One permission a group level covers, in the vocabulary plugins already declare: a page (`access`), an action of a
|
|
19
|
+
* settings item, or a business action. The application maps each to an authorization resource and action.
|
|
20
|
+
*/
|
|
21
|
+
export type AccessRef = {
|
|
22
|
+
readonly kind: 'page';
|
|
23
|
+
readonly id: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'settings';
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly action: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'business';
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly action: string;
|
|
32
|
+
};
|
|
33
|
+
/** The section a group is listed under, as GitHub lists repository, organization and account permissions. */
|
|
34
|
+
export type KeyScopeCategory = 'business' | 'administration' | 'account';
|
|
35
|
+
export interface KeyScopeGroupDeclaration {
|
|
36
|
+
/** `<plugin>.<area>`, such as `releases.apps`. */
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly category: KeyScopeCategory;
|
|
39
|
+
readonly title: LocalizedText;
|
|
40
|
+
readonly description?: LocalizedText;
|
|
41
|
+
/** What each level adds; a level left out is not offered. */
|
|
42
|
+
readonly levels: Readonly<Partial<Record<KeyScopeLevel, readonly AccessRef[]>>>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the group may be limited to some records of a business ("only selected Apps"). Only the plugin's own
|
|
45
|
+
* guards can enforce this, through `KeyScope.objects(business)`; declare it only where they do.
|
|
46
|
+
*/
|
|
47
|
+
readonly objects?: {
|
|
48
|
+
/** The business whose records are picked, such as `rel.apps`. At most one group per business. */
|
|
49
|
+
readonly business: string;
|
|
50
|
+
readonly title: LocalizedText;
|
|
51
|
+
/** Actions kept only while every record is in scope, such as creating one. */
|
|
52
|
+
readonly allowsUnscoped?: readonly AccessRef[];
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface KeyScopePresetDeclaration {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly title: LocalizedText;
|
|
58
|
+
readonly description?: LocalizedText;
|
|
59
|
+
readonly groups: Readonly<Record<string, {
|
|
60
|
+
readonly level: KeyScopeLevel;
|
|
61
|
+
readonly objects?: 'all' | 'pick';
|
|
62
|
+
}>>;
|
|
63
|
+
/** The expiry the preset suggests, in days; null for none. */
|
|
64
|
+
readonly expiresInDays?: number | null;
|
|
65
|
+
}
|
|
66
|
+
/** One group of a key's scope: the level, and the record ids it is limited to (omitted or `all` for every record). */
|
|
67
|
+
export interface KeyScopeGroupGrant {
|
|
68
|
+
readonly level: KeyScopeLevel;
|
|
69
|
+
readonly objects?: 'all' | readonly string[];
|
|
70
|
+
}
|
|
71
|
+
/** What a key's scope holds, group by group; a group left out grants nothing. */
|
|
72
|
+
export interface KeyScopeInput {
|
|
73
|
+
readonly groups: Readonly<Record<string, KeyScopeGroupGrant>>;
|
|
74
|
+
}
|
|
75
|
+
/** The storage format's version, kept under `$v`. */
|
|
76
|
+
export declare const KEY_SCOPE_FORMAT_VERSION = "1";
|
|
77
|
+
/** The records a group grant is limited to; undefined when it reaches every record. */
|
|
78
|
+
export declare function pickedObjects(grant: KeyScopeGroupGrant): readonly string[] | undefined;
|
|
79
|
+
/** The levels up to and including `level`. */
|
|
80
|
+
export declare function levelsUpTo(level: KeyScopeLevel): KeyScopeLevel[];
|
|
81
|
+
/**
|
|
82
|
+
* A scope as Better Auth's `permissions` column holds it: each group's levels expanded (`"releases.apps":
|
|
83
|
+
* ["read","write"]`), picked records under `"<group>@"`, and the format version under `$v`. Better Auth treats
|
|
84
|
+
* `permissions` as server-only on create and update, so a key's holder cannot widen it through `/api-key/update`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function encodeKeyScope(scope: KeyScopeInput): Record<string, string[]>;
|
|
87
|
+
/** The scope a `permissions` value holds; null for a key without one. A value in an unknown format grants nothing. */
|
|
88
|
+
export declare function decodeKeyScope(permissions: unknown): KeyScopeInput | null;
|
|
89
|
+
/** A group as an editor shows it, with what the asking user holds at each level today. */
|
|
90
|
+
export interface KeyScopeGroupView {
|
|
91
|
+
readonly id: string;
|
|
92
|
+
readonly category: KeyScopeCategory;
|
|
93
|
+
readonly title: LocalizedText;
|
|
94
|
+
readonly description: LocalizedText | null;
|
|
95
|
+
/** The levels offered, lowest first. */
|
|
96
|
+
readonly levels: readonly KeyScopeLevel[];
|
|
97
|
+
/** What each level adds, for a hover list. */
|
|
98
|
+
readonly access: Readonly<Partial<Record<KeyScopeLevel, readonly AccessRef[]>>>;
|
|
99
|
+
/** Whether the user for whom the key is made holds everything up to each level; false shows it greyed out. */
|
|
100
|
+
readonly held: Readonly<Partial<Record<KeyScopeLevel, boolean>>>;
|
|
101
|
+
readonly objects: {
|
|
102
|
+
readonly business: string;
|
|
103
|
+
readonly title: LocalizedText;
|
|
104
|
+
} | null;
|
|
105
|
+
}
|
|
106
|
+
export interface KeyScopePresetView {
|
|
107
|
+
readonly id: string;
|
|
108
|
+
readonly title: LocalizedText;
|
|
109
|
+
readonly description: LocalizedText | null;
|
|
110
|
+
readonly groups: KeyScopePresetDeclaration['groups'];
|
|
111
|
+
readonly expiresInDays: number | null;
|
|
112
|
+
}
|
|
113
|
+
/** `GET /api/apiKeys/scopeOptions`: what the editor offers. Empty `groups` means scoped keys are not offered. */
|
|
114
|
+
export interface KeyScopeOptions {
|
|
115
|
+
readonly groups: readonly KeyScopeGroupView[];
|
|
116
|
+
readonly presets: readonly KeyScopePresetView[];
|
|
117
|
+
/** The longest a scoped key may live, in days; null when "never expires" is allowed. */
|
|
118
|
+
readonly maxScopedKeyDays: number | null;
|
|
119
|
+
/** The expiry the editor proposes, in days. */
|
|
120
|
+
readonly defaultExpiresInDays: number;
|
|
121
|
+
/**
|
|
122
|
+
* Whether the asking person may create keys of their own (`ScopedApiKeys.setOwnKeyPolicy`); sent by
|
|
123
|
+
* `/api/apiKeys/scopeOptions`, absent elsewhere.
|
|
124
|
+
*/
|
|
125
|
+
readonly mayCreate?: boolean;
|
|
126
|
+
}
|
|
127
|
+
/** A record a group may be limited to. */
|
|
128
|
+
export interface KeyScopeObject {
|
|
129
|
+
readonly id: string;
|
|
130
|
+
readonly title: string;
|
|
131
|
+
readonly description?: string;
|
|
132
|
+
}
|
|
133
|
+
/** A key as a list shows it: never its secret or hash. */
|
|
134
|
+
export interface ApiKeyView {
|
|
135
|
+
readonly id: string;
|
|
136
|
+
readonly name: string | null;
|
|
137
|
+
readonly description: string | null;
|
|
138
|
+
/** The first characters of the key, to recognize it. */
|
|
139
|
+
readonly start: string | null;
|
|
140
|
+
readonly enabled: boolean;
|
|
141
|
+
readonly createdAt: string;
|
|
142
|
+
readonly expiresAt: string | null;
|
|
143
|
+
/** When the key was last used, as Better Auth records it. */
|
|
144
|
+
readonly lastUsedAt: string | null;
|
|
145
|
+
/** Null for a key that is its owner in full. */
|
|
146
|
+
readonly scope: KeyScopeInput | null;
|
|
147
|
+
}
|
|
148
|
+
/** `POST` a key: a name, an optional description, an expiry in days (null for none) and a scope (null for full). */
|
|
149
|
+
export interface CreateApiKeyRequest {
|
|
150
|
+
readonly name: string;
|
|
151
|
+
readonly description?: string | null;
|
|
152
|
+
readonly expiresInDays: number | null;
|
|
153
|
+
readonly scope: KeyScopeInput | null;
|
|
154
|
+
}
|
|
155
|
+
/** A key just created or rotated: its secret, shown once. */
|
|
156
|
+
export interface CreatedApiKey {
|
|
157
|
+
readonly key: ApiKeyView;
|
|
158
|
+
readonly secret: string;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../shared/scopes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key scopes as data: what a plugin declares it offers (permission groups and presets), what a key's scope holds, and
|
|
3
|
+
* how a scope is stored in Better Auth's `permissions` column. Nothing here imports another plugin, so a plugin that
|
|
4
|
+
* contributes a group writes a literal of the same shape without depending on this package.
|
|
5
|
+
*
|
|
6
|
+
* A scope only ever narrows: a key's effective permission is its owner's current permission intersected with the
|
|
7
|
+
* scope. A key without a scope (`permissions = null`) is its owner, exactly as before scopes existed.
|
|
8
|
+
*/
|
|
9
|
+
export const KEY_SCOPE_LEVELS = [
|
|
10
|
+
'read',
|
|
11
|
+
'write',
|
|
12
|
+
'admin',
|
|
13
|
+
];
|
|
14
|
+
/** The storage format's version, kept under `$v`. */
|
|
15
|
+
export const KEY_SCOPE_FORMAT_VERSION = '1';
|
|
16
|
+
/** The records a group grant is limited to; undefined when it reaches every record. */
|
|
17
|
+
export function pickedObjects(grant) {
|
|
18
|
+
return grant.objects === undefined || grant.objects === 'all'
|
|
19
|
+
? undefined
|
|
20
|
+
: grant.objects;
|
|
21
|
+
}
|
|
22
|
+
/** The levels up to and including `level`. */
|
|
23
|
+
export function levelsUpTo(level) {
|
|
24
|
+
return KEY_SCOPE_LEVELS.slice(0, KEY_SCOPE_LEVELS.indexOf(level) + 1);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A scope as Better Auth's `permissions` column holds it: each group's levels expanded (`"releases.apps":
|
|
28
|
+
* ["read","write"]`), picked records under `"<group>@"`, and the format version under `$v`. Better Auth treats
|
|
29
|
+
* `permissions` as server-only on create and update, so a key's holder cannot widen it through `/api-key/update`.
|
|
30
|
+
*/
|
|
31
|
+
export function encodeKeyScope(scope) {
|
|
32
|
+
const encoded = {
|
|
33
|
+
$v: [KEY_SCOPE_FORMAT_VERSION],
|
|
34
|
+
};
|
|
35
|
+
for (const [id, grant] of Object.entries(scope.groups)) {
|
|
36
|
+
encoded[id] = levelsUpTo(grant.level);
|
|
37
|
+
const picked = pickedObjects(grant);
|
|
38
|
+
if (picked)
|
|
39
|
+
encoded[`${id}@`] = [...picked];
|
|
40
|
+
}
|
|
41
|
+
return encoded;
|
|
42
|
+
}
|
|
43
|
+
/** The scope a `permissions` value holds; null for a key without one. A value in an unknown format grants nothing. */
|
|
44
|
+
export function decodeKeyScope(permissions) {
|
|
45
|
+
const value = typeof permissions === 'string' ? safeParse(permissions) : permissions;
|
|
46
|
+
if (value === null || value === undefined)
|
|
47
|
+
return null;
|
|
48
|
+
if (typeof value !== 'object' || Array.isArray(value))
|
|
49
|
+
return { groups: {} };
|
|
50
|
+
const record = value;
|
|
51
|
+
const version = record.$v;
|
|
52
|
+
if (!Array.isArray(version) || version[0] !== KEY_SCOPE_FORMAT_VERSION)
|
|
53
|
+
return { groups: {} };
|
|
54
|
+
const groups = {};
|
|
55
|
+
for (const [id, levels] of Object.entries(record)) {
|
|
56
|
+
if (id === '$v' || id.endsWith('@') || !Array.isArray(levels))
|
|
57
|
+
continue;
|
|
58
|
+
const level = [...KEY_SCOPE_LEVELS]
|
|
59
|
+
.reverse()
|
|
60
|
+
.find((candidate) => levels.includes(candidate));
|
|
61
|
+
if (!level)
|
|
62
|
+
continue;
|
|
63
|
+
const objects = record[`${id}@`];
|
|
64
|
+
groups[id] = {
|
|
65
|
+
level,
|
|
66
|
+
...(Array.isArray(objects)
|
|
67
|
+
? {
|
|
68
|
+
objects: objects.filter((item) => typeof item === 'string'),
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { groups };
|
|
74
|
+
}
|
|
75
|
+
function safeParse(text) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=scopes.js.map
|