@bpmnkit/profiles 0.0.5
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/README.md +139 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +19 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/modeler.d.ts +3 -0
- package/dist/modeler.js +95 -0
- package/dist/profile.d.ts +35 -0
- package/dist/profile.js +153 -0
- package/dist/token.d.ts +9 -0
- package/dist/token.js +51 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/bpmn-sdk/monorepo/main/doc/logos/logo-2-gateway.svg" width="72" height="72" alt="BPMN Kit logo">
|
|
3
|
+
<h1>@bpmnkit/profiles</h1>
|
|
4
|
+
<p>Shared auth, profile storage, and client factories for the BPMN Kit CLI and proxy server</p>
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@bpmnkit/profiles)
|
|
7
|
+
[](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
|
|
8
|
+
[](https://github.com/bpmnkit/monorepo)
|
|
9
|
+
|
|
10
|
+
[Documentation](https://bpmn-sdk-docs.pages.dev) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/profiles/CHANGELOG.md)
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`@bpmnkit/profiles` is the shared layer that connects the `casen` CLI with the local proxy server. It handles profile CRUD (read/write to `~/.config/casen/config.json`), creates typed `CamundaClient` instances from stored profiles, and resolves Authorization headers for any supported auth type.
|
|
18
|
+
|
|
19
|
+
You do not need this package if you are connecting directly to Camunda using `@bpmnkit/api`. It is intended for tooling that needs to share authentication state with the CLI.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Profile CRUD** — list, get, save, delete, and activate named profiles stored in `~/.config/casen/config.json`
|
|
24
|
+
- **Client factory** — `createClientFromProfile(name?)` creates a ready-to-use `CamundaClient` from the active or named profile
|
|
25
|
+
- **Auth header resolution** — `getAuthHeader(config)` returns the correct `Authorization` header string for Bearer, Basic, and OAuth2 auth types
|
|
26
|
+
- **OAuth2 token caching** — tokens are cached in memory and refreshed 60 seconds before expiry; no extra files written
|
|
27
|
+
- **XDG-aware** — profile file path resolves to the correct platform directory (Linux XDG, macOS, Windows AppData)
|
|
28
|
+
- **Zero UI dependencies** — no TUI or CLI dependencies; plain Node.js
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm install @bpmnkit/profiles
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Create a `CamundaClient` from the active profile
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createClientFromProfile } from "@bpmnkit/profiles"
|
|
42
|
+
|
|
43
|
+
// Uses the currently active profile from ~/.config/casen/config.json
|
|
44
|
+
const client = createClientFromProfile()
|
|
45
|
+
|
|
46
|
+
const instances = await client.processInstance.searchProcessInstances({})
|
|
47
|
+
console.log(instances.page.totalItems)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Use a named profile
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const client = createClientFromProfile("production")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Resolve an auth header directly
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { getActiveProfile, getAuthHeader } from "@bpmnkit/profiles"
|
|
60
|
+
|
|
61
|
+
const profile = getActiveProfile()
|
|
62
|
+
if (profile) {
|
|
63
|
+
const header = await getAuthHeader(profile.config)
|
|
64
|
+
// "Bearer eyJ..." or "Basic dXNlcjpwYXNz" or ""
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Manage profiles programmatically
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { listProfiles, saveProfile, useProfile, deleteProfile } from "@bpmnkit/profiles"
|
|
72
|
+
|
|
73
|
+
// List all profiles
|
|
74
|
+
const profiles = listProfiles()
|
|
75
|
+
|
|
76
|
+
// Save a new profile
|
|
77
|
+
saveProfile({
|
|
78
|
+
name: "local",
|
|
79
|
+
apiType: "self-managed",
|
|
80
|
+
config: {
|
|
81
|
+
baseUrl: "http://localhost:8080/v2",
|
|
82
|
+
auth: { type: "basic", username: "admin", password: "admin" },
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Activate a profile
|
|
87
|
+
useProfile("local")
|
|
88
|
+
|
|
89
|
+
// Delete a profile
|
|
90
|
+
deleteProfile("old-profile")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### Profile Management
|
|
96
|
+
|
|
97
|
+
| Export | Description |
|
|
98
|
+
|--------|-------------|
|
|
99
|
+
| `listProfiles()` | Returns all stored profiles |
|
|
100
|
+
| `getProfile(name)` | Returns a profile by name, or `undefined` |
|
|
101
|
+
| `getActiveProfile()` | Returns the currently active profile |
|
|
102
|
+
| `getActiveName()` | Returns the active profile name |
|
|
103
|
+
| `saveProfile(profile)` | Create or update a profile |
|
|
104
|
+
| `deleteProfile(name)` | Remove a profile |
|
|
105
|
+
| `useProfile(name)` | Set the active profile |
|
|
106
|
+
| `getConfigFilePath()` | Returns the full path to the config file |
|
|
107
|
+
|
|
108
|
+
### Client Factories
|
|
109
|
+
|
|
110
|
+
| Export | Description |
|
|
111
|
+
|--------|-------------|
|
|
112
|
+
| `createClientFromProfile(name?)` | `CamundaClient` from the active or named profile |
|
|
113
|
+
| `createAdminClientFromProfile(name?)` | `AdminApiClient` from the active or named profile |
|
|
114
|
+
|
|
115
|
+
### Auth
|
|
116
|
+
|
|
117
|
+
| Export | Description |
|
|
118
|
+
|--------|-------------|
|
|
119
|
+
| `getAuthHeader(config)` | Resolves an `Authorization` header string for any auth type |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Related Packages
|
|
124
|
+
|
|
125
|
+
| Package | Description |
|
|
126
|
+
|---------|-------------|
|
|
127
|
+
| [`@bpmnkit/core`](https://www.npmjs.com/package/@bpmnkit/core) | BPMN/DMN/Form parser, builder, layout engine |
|
|
128
|
+
| [`@bpmnkit/canvas`](https://www.npmjs.com/package/@bpmnkit/canvas) | Zero-dependency SVG BPMN viewer |
|
|
129
|
+
| [`@bpmnkit/editor`](https://www.npmjs.com/package/@bpmnkit/editor) | Full-featured interactive BPMN editor |
|
|
130
|
+
| [`@bpmnkit/engine`](https://www.npmjs.com/package/@bpmnkit/engine) | Lightweight BPMN process execution engine |
|
|
131
|
+
| [`@bpmnkit/feel`](https://www.npmjs.com/package/@bpmnkit/feel) | FEEL expression language parser & evaluator |
|
|
132
|
+
| [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
|
|
133
|
+
| [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
|
|
134
|
+
| [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
|
|
135
|
+
| [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring & operations frontend for Camunda clusters |
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
[MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © bpmn-sdk
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AdminApiClient, CamundaClient } from "@bpmnkit/api";
|
|
2
|
+
export declare function createClientFromProfile(profileName?: string): CamundaClient;
|
|
3
|
+
export declare function createAdminClientFromProfile(profileName?: string): AdminApiClient;
|
|
4
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AdminApiClient, CamundaClient } from "@bpmnkit/api";
|
|
2
|
+
import { getActiveProfile, getProfile } from "./profile.js";
|
|
3
|
+
function requireProfile(profileName) {
|
|
4
|
+
const profile = profileName ? getProfile(profileName) : getActiveProfile();
|
|
5
|
+
if (!profile) {
|
|
6
|
+
if (profileName) {
|
|
7
|
+
throw new Error(`Profile "${profileName}" not found. Run \`casen profile list\` to see available profiles.`);
|
|
8
|
+
}
|
|
9
|
+
throw new Error("No active profile. Create one with:\n\n casen profile create <name> --base-url <url> --auth-type bearer --token <token>\n");
|
|
10
|
+
}
|
|
11
|
+
return profile;
|
|
12
|
+
}
|
|
13
|
+
export function createClientFromProfile(profileName) {
|
|
14
|
+
return new CamundaClient(requireProfile(profileName).config);
|
|
15
|
+
}
|
|
16
|
+
export function createAdminClientFromProfile(profileName) {
|
|
17
|
+
return new AdminApiClient(requireProfile(profileName).config);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=client.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ApiType, AuditEntry, Profile, Settings } from "./profile.js";
|
|
2
|
+
export { listModelerProfiles } from "./modeler.js";
|
|
3
|
+
export { appendAuditEntry, clearAuditLog, deleteProfile, getActiveProfile, getActiveName, getAuditLog, getConfigFilePath, getProfile, getSettings, listProfiles, saveProfile, saveSettings, useProfile, } from "./profile.js";
|
|
4
|
+
export { createAdminClientFromProfile, createClientFromProfile } from "./client.js";
|
|
5
|
+
export { getAuthHeader } from "./token.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { listModelerProfiles } from "./modeler.js";
|
|
2
|
+
export { appendAuditEntry, clearAuditLog, deleteProfile, getActiveProfile, getActiveName, getAuditLog, getConfigFilePath, getProfile, getSettings, listProfiles, saveProfile, saveSettings, useProfile, } from "./profile.js";
|
|
3
|
+
export { createAdminClientFromProfile, createClientFromProfile } from "./client.js";
|
|
4
|
+
export { getAuthHeader } from "./token.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
package/dist/modeler.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// ─── Modeler config directory ─────────────────────────────────────────────────
|
|
5
|
+
function modelerConfigDir() {
|
|
6
|
+
const p = platform();
|
|
7
|
+
if (p === "win32")
|
|
8
|
+
return join(process.env.APPDATA ?? homedir(), "camunda-modeler");
|
|
9
|
+
if (p === "darwin")
|
|
10
|
+
return join(homedir(), "Library", "Application Support", "camunda-modeler");
|
|
11
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "camunda-modeler");
|
|
12
|
+
}
|
|
13
|
+
// ─── Auth mapping ─────────────────────────────────────────────────────────────
|
|
14
|
+
function toAuth(conn) {
|
|
15
|
+
const targetType = typeof conn.targetType === "string" ? conn.targetType : "";
|
|
16
|
+
const authType = typeof conn.authType === "string" ? conn.authType : "";
|
|
17
|
+
// Camunda Cloud (SaaS): use OAuth2 with cloud token endpoint when credentials present
|
|
18
|
+
if (targetType === "camundaCloud" &&
|
|
19
|
+
typeof conn.camundaCloudClientId === "string" &&
|
|
20
|
+
conn.camundaCloudClientId &&
|
|
21
|
+
typeof conn.camundaCloudClientSecret === "string" &&
|
|
22
|
+
conn.camundaCloudClientSecret) {
|
|
23
|
+
return {
|
|
24
|
+
type: "oauth2",
|
|
25
|
+
clientId: conn.camundaCloudClientId,
|
|
26
|
+
clientSecret: conn.camundaCloudClientSecret,
|
|
27
|
+
tokenUrl: "https://login.cloud.camunda.io/oauth/token",
|
|
28
|
+
audience: "zeebe.camunda.io",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Self-managed OAuth2
|
|
32
|
+
if ((authType === "clientCredentials" || authType === "oauth2") &&
|
|
33
|
+
typeof conn.clientId === "string" &&
|
|
34
|
+
conn.clientId &&
|
|
35
|
+
typeof conn.clientSecret === "string" &&
|
|
36
|
+
conn.clientSecret) {
|
|
37
|
+
return {
|
|
38
|
+
type: "oauth2",
|
|
39
|
+
clientId: conn.clientId,
|
|
40
|
+
clientSecret: conn.clientSecret,
|
|
41
|
+
tokenUrl: typeof conn.tokenUrl === "string" ? conn.tokenUrl : "",
|
|
42
|
+
audience: typeof conn.audience === "string" ? conn.audience : undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Bearer token
|
|
46
|
+
if (authType === "bearer" && typeof conn.bearerToken === "string" && conn.bearerToken) {
|
|
47
|
+
return { type: "bearer", token: conn.bearerToken };
|
|
48
|
+
}
|
|
49
|
+
// Basic auth
|
|
50
|
+
if (authType === "basic" &&
|
|
51
|
+
typeof conn.username === "string" &&
|
|
52
|
+
conn.username &&
|
|
53
|
+
typeof conn.password === "string") {
|
|
54
|
+
return { type: "basic", username: conn.username, password: conn.password };
|
|
55
|
+
}
|
|
56
|
+
return { type: "none" };
|
|
57
|
+
}
|
|
58
|
+
function toProfile(conn) {
|
|
59
|
+
const name = typeof conn.name === "string" && conn.name ? conn.name : null;
|
|
60
|
+
if (!name)
|
|
61
|
+
return null;
|
|
62
|
+
const baseUrl = typeof conn.contactPoint === "string" && conn.contactPoint ? conn.contactPoint : undefined;
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
apiType: "c8",
|
|
66
|
+
config: { baseUrl, auth: toAuth(conn) },
|
|
67
|
+
createdAt: null,
|
|
68
|
+
source: "modeler",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// ─── Public ───────────────────────────────────────────────────────────────────
|
|
72
|
+
export function listModelerProfiles() {
|
|
73
|
+
try {
|
|
74
|
+
const raw = readFileSync(join(modelerConfigDir(), "settings.json"), "utf8");
|
|
75
|
+
const settings = JSON.parse(raw);
|
|
76
|
+
if (!settings || typeof settings !== "object")
|
|
77
|
+
return [];
|
|
78
|
+
const connections = settings["connectionManagerPlugin.c8connections"];
|
|
79
|
+
if (!Array.isArray(connections))
|
|
80
|
+
return [];
|
|
81
|
+
const profiles = [];
|
|
82
|
+
for (const conn of connections) {
|
|
83
|
+
if (!conn || typeof conn !== "object")
|
|
84
|
+
continue;
|
|
85
|
+
const profile = toProfile(conn);
|
|
86
|
+
if (profile)
|
|
87
|
+
profiles.push(profile);
|
|
88
|
+
}
|
|
89
|
+
return profiles;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=modeler.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CamundaClientInput } from "@bpmnkit/api";
|
|
2
|
+
export type ApiType = "c8" | "admin";
|
|
3
|
+
export interface Profile {
|
|
4
|
+
name: string;
|
|
5
|
+
apiType: ApiType;
|
|
6
|
+
config: CamundaClientInput;
|
|
7
|
+
createdAt: string | null;
|
|
8
|
+
source?: "modeler";
|
|
9
|
+
}
|
|
10
|
+
export interface AuditEntry {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
group: string;
|
|
13
|
+
command: string;
|
|
14
|
+
positional: string[];
|
|
15
|
+
flags: Record<string, string | boolean | number>;
|
|
16
|
+
status: "ok" | "error";
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Settings {
|
|
20
|
+
auditLogSize: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function listProfiles(): Profile[];
|
|
23
|
+
export declare function getProfile(name: string): Profile | undefined;
|
|
24
|
+
export declare function getActiveProfile(): Profile | undefined;
|
|
25
|
+
export declare function getActiveName(): string | null;
|
|
26
|
+
export declare function saveProfile(name: string, config: CamundaClientInput, apiType?: ApiType): void;
|
|
27
|
+
export declare function deleteProfile(name: string): boolean;
|
|
28
|
+
export declare function useProfile(name: string): boolean;
|
|
29
|
+
export declare function getConfigFilePath(): string;
|
|
30
|
+
export declare function getSettings(): Settings;
|
|
31
|
+
export declare function saveSettings(settings: Partial<Settings>): void;
|
|
32
|
+
export declare function appendAuditEntry(profile: string, entry: Omit<AuditEntry, "timestamp">): void;
|
|
33
|
+
export declare function getAuditLog(profile?: string): AuditEntry[];
|
|
34
|
+
export declare function clearAuditLog(profile?: string): void;
|
|
35
|
+
//# sourceMappingURL=profile.d.ts.map
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { listModelerProfiles } from "./modeler.js";
|
|
5
|
+
const DEFAULT_AUDIT_LOG_SIZE = 15;
|
|
6
|
+
// ─── Config directory ─────────────────────────────────────────────────────────
|
|
7
|
+
function configDir() {
|
|
8
|
+
const p = platform();
|
|
9
|
+
if (p === "win32")
|
|
10
|
+
return join(process.env.APPDATA ?? homedir(), "casen");
|
|
11
|
+
if (p === "darwin")
|
|
12
|
+
return join(homedir(), "Library", "Application Support", "casen");
|
|
13
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "casen");
|
|
14
|
+
}
|
|
15
|
+
function configFilePath() {
|
|
16
|
+
return join(configDir(), "config.json");
|
|
17
|
+
}
|
|
18
|
+
// ─── Read / write ─────────────────────────────────────────────────────────────
|
|
19
|
+
function readStore() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(configFilePath(), "utf8");
|
|
22
|
+
const store = JSON.parse(raw);
|
|
23
|
+
if (!store.meta)
|
|
24
|
+
store.meta = {};
|
|
25
|
+
return store;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { profiles: {}, active: null, meta: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function writeStore(store) {
|
|
32
|
+
const dir = configDir();
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
writeFileSync(configFilePath(), JSON.stringify(store, null, 2), "utf8");
|
|
35
|
+
}
|
|
36
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
37
|
+
export function listProfiles() {
|
|
38
|
+
const store = readStore();
|
|
39
|
+
const own = Object.entries(store.profiles).map(([name, config]) => ({
|
|
40
|
+
name,
|
|
41
|
+
apiType: store.meta[name]?.apiType ?? "c8",
|
|
42
|
+
config,
|
|
43
|
+
createdAt: store.meta[name]?.createdAt ?? null,
|
|
44
|
+
}));
|
|
45
|
+
// Merge Camunda Modeler connections; own profiles take precedence by name
|
|
46
|
+
const ownNames = new Set(own.map((p) => p.name));
|
|
47
|
+
const modeler = listModelerProfiles().filter((p) => !ownNames.has(p.name));
|
|
48
|
+
return [...own, ...modeler];
|
|
49
|
+
}
|
|
50
|
+
export function getProfile(name) {
|
|
51
|
+
const store = readStore();
|
|
52
|
+
const config = store.profiles[name];
|
|
53
|
+
if (!config)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
name,
|
|
57
|
+
apiType: store.meta[name]?.apiType ?? "c8",
|
|
58
|
+
config,
|
|
59
|
+
createdAt: store.meta[name]?.createdAt ?? null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function getActiveProfile() {
|
|
63
|
+
const store = readStore();
|
|
64
|
+
if (!store.active)
|
|
65
|
+
return undefined;
|
|
66
|
+
return getProfile(store.active);
|
|
67
|
+
}
|
|
68
|
+
export function getActiveName() {
|
|
69
|
+
return readStore().active;
|
|
70
|
+
}
|
|
71
|
+
export function saveProfile(name, config, apiType = "c8") {
|
|
72
|
+
const store = readStore();
|
|
73
|
+
store.profiles[name] = config;
|
|
74
|
+
if (!store.meta[name]) {
|
|
75
|
+
store.meta[name] = { createdAt: new Date().toISOString(), apiType };
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
store.meta[name].apiType = apiType;
|
|
79
|
+
}
|
|
80
|
+
// Auto-activate if this is the first profile
|
|
81
|
+
if (store.active === null)
|
|
82
|
+
store.active = name;
|
|
83
|
+
writeStore(store);
|
|
84
|
+
}
|
|
85
|
+
export function deleteProfile(name) {
|
|
86
|
+
const store = readStore();
|
|
87
|
+
if (!(name in store.profiles))
|
|
88
|
+
return false;
|
|
89
|
+
const { [name]: _removed, ...rest } = store.profiles;
|
|
90
|
+
store.profiles = rest;
|
|
91
|
+
if (store.active === name) {
|
|
92
|
+
const remaining = Object.keys(store.profiles);
|
|
93
|
+
store.active = remaining.length > 0 ? (remaining[0] ?? null) : null;
|
|
94
|
+
}
|
|
95
|
+
writeStore(store);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
export function useProfile(name) {
|
|
99
|
+
const store = readStore();
|
|
100
|
+
if (!(name in store.profiles))
|
|
101
|
+
return false;
|
|
102
|
+
store.active = name;
|
|
103
|
+
writeStore(store);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
export function getConfigFilePath() {
|
|
107
|
+
return configFilePath();
|
|
108
|
+
}
|
|
109
|
+
// ─── Settings ─────────────────────────────────────────────────────────────────
|
|
110
|
+
export function getSettings() {
|
|
111
|
+
const store = readStore();
|
|
112
|
+
return { auditLogSize: store.settings?.auditLogSize ?? DEFAULT_AUDIT_LOG_SIZE };
|
|
113
|
+
}
|
|
114
|
+
export function saveSettings(settings) {
|
|
115
|
+
const store = readStore();
|
|
116
|
+
store.settings = { ...store.settings, ...settings };
|
|
117
|
+
writeStore(store);
|
|
118
|
+
}
|
|
119
|
+
// ─── Audit log ────────────────────────────────────────────────────────────────
|
|
120
|
+
export function appendAuditEntry(profile, entry) {
|
|
121
|
+
const store = readStore();
|
|
122
|
+
const size = store.settings?.auditLogSize ?? DEFAULT_AUDIT_LOG_SIZE;
|
|
123
|
+
const log = store.auditLog ?? {};
|
|
124
|
+
const existing = log[profile] ?? [];
|
|
125
|
+
const updated = [...existing, { ...entry, timestamp: new Date().toISOString() }];
|
|
126
|
+
log[profile] = updated.slice(-size);
|
|
127
|
+
store.auditLog = log;
|
|
128
|
+
writeStore(store);
|
|
129
|
+
}
|
|
130
|
+
export function getAuditLog(profile) {
|
|
131
|
+
const store = readStore();
|
|
132
|
+
if (!store.auditLog)
|
|
133
|
+
return [];
|
|
134
|
+
if (profile)
|
|
135
|
+
return store.auditLog[profile] ?? [];
|
|
136
|
+
return Object.values(store.auditLog)
|
|
137
|
+
.flat()
|
|
138
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
139
|
+
}
|
|
140
|
+
export function clearAuditLog(profile) {
|
|
141
|
+
const store = readStore();
|
|
142
|
+
if (!store.auditLog)
|
|
143
|
+
return;
|
|
144
|
+
if (profile) {
|
|
145
|
+
const { [profile]: _removed, ...rest } = store.auditLog;
|
|
146
|
+
store.auditLog = rest;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
store.auditLog = {};
|
|
150
|
+
}
|
|
151
|
+
writeStore(store);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=profile.js.map
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CamundaClientInput } from "@bpmnkit/api";
|
|
2
|
+
/**
|
|
3
|
+
* Returns an Authorization header value for the given client config.
|
|
4
|
+
* Returns an empty string for `auth.type === "none"` or if auth is unset.
|
|
5
|
+
*
|
|
6
|
+
* For OAuth2, tokens are cached in memory and refreshed 60 seconds before expiry.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAuthHeader(config: CamundaClientInput): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=token.d.ts.map
|
package/dist/token.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Simple in-memory token cache keyed by clientId
|
|
2
|
+
const tokenCache = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Returns an Authorization header value for the given client config.
|
|
5
|
+
* Returns an empty string for `auth.type === "none"` or if auth is unset.
|
|
6
|
+
*
|
|
7
|
+
* For OAuth2, tokens are cached in memory and refreshed 60 seconds before expiry.
|
|
8
|
+
*/
|
|
9
|
+
export async function getAuthHeader(config) {
|
|
10
|
+
const auth = config.auth;
|
|
11
|
+
if (!auth || auth.type === "none")
|
|
12
|
+
return "";
|
|
13
|
+
if (auth.type === "bearer")
|
|
14
|
+
return `Bearer ${auth.token}`;
|
|
15
|
+
if (auth.type === "basic") {
|
|
16
|
+
const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
17
|
+
return `Basic ${encoded}`;
|
|
18
|
+
}
|
|
19
|
+
if (auth.type === "oauth2") {
|
|
20
|
+
const token = await fetchOAuth2Token(auth.clientId, auth.clientSecret, auth.tokenUrl, auth.audience, auth.scope);
|
|
21
|
+
return `Bearer ${token}`;
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
async function fetchOAuth2Token(clientId, clientSecret, tokenUrl, audience, scope) {
|
|
26
|
+
const cached = tokenCache.get(clientId);
|
|
27
|
+
if (cached && cached.expiresAt > Date.now() + 60_000)
|
|
28
|
+
return cached.token;
|
|
29
|
+
const body = new URLSearchParams({
|
|
30
|
+
grant_type: "client_credentials",
|
|
31
|
+
client_id: clientId,
|
|
32
|
+
client_secret: clientSecret,
|
|
33
|
+
});
|
|
34
|
+
if (audience)
|
|
35
|
+
body.set("audience", audience);
|
|
36
|
+
if (scope)
|
|
37
|
+
body.set("scope", scope);
|
|
38
|
+
const res = await fetch(tokenUrl, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
body,
|
|
41
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(`OAuth2 token request failed: ${res.status} ${res.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
const json = (await res.json());
|
|
47
|
+
const expiresIn = json.expires_in ?? 3600;
|
|
48
|
+
tokenCache.set(clientId, { token: json.access_token, expiresAt: Date.now() + expiresIn * 1000 });
|
|
49
|
+
return json.access_token;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=token.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpmnkit/profiles",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Shared profile storage and auth client factory for @bpmn-sdk CLI and proxy server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist/**/*.js", "dist/**/*.d.ts"],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"check": "biome check ."
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@bpmnkit/api": "workspace:*"
|
|
25
|
+
},
|
|
26
|
+
"keywords": ["bpmn", "camunda", "profiles", "authentication", "oauth2", "cli", "typescript"],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/bpmnkit/monorepo"
|
|
33
|
+
}
|
|
34
|
+
}
|