@happyvertical/smrt-cli 0.53.4 → 0.53.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/AGENTS.md +2 -0
- package/README.md +24 -0
- package/agents/app-commands.md +28 -3
- package/dist/{ApiKey-BZmbOTnL-DPPF6Swn.js → ApiKey-BZmbOTnL-BbZupUZ3.js} +2 -2
- package/dist/ApiKeyCollection-Bfb_nw81-DYAZwx0O.js +87 -0
- package/dist/AuditLogCollection-B1YonCAP-Bkj2Ezon.js +193 -0
- package/dist/{NostrIdentity-n70E0vck-D60BSg6H.js → NostrIdentity-n70E0vck-Di3_eNCq.js} +4 -4
- package/dist/NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js +127 -0
- package/dist/{OidcIdentity-Cb1bxreM-B2eV2ahq.js → OidcIdentity-Cb1bxreM-LXmC7uAf.js} +2 -2
- package/dist/OidcIdentityCollection-TV8VdK69-Cv1ZTeuk.js +86 -0
- package/dist/ProfileAssetCollection-B9hacUJa-CPuaWLk4.js +101 -0
- package/dist/{ProfileCollection-DuJT7Mum-B3Uhqphs.js → ProfileCollection-DuJT7Mum-nXbHrPpX.js} +31 -31
- package/dist/ProfileLinkCollection-4uNURz32-CbKZYmYm.js +233 -0
- package/dist/ProfileMetadataCollection-DQhbfEfa-_1rQCLTR.js +108 -0
- package/dist/ProfileMetafieldCollection-CoBuM_5G-CPjW3l5r.js +145 -0
- package/dist/ProfileRelationshipCollection-DUVgaIqf-RCDqYz6O.js +161 -0
- package/dist/ProfileRelationshipTermCollection-DtA9OM23-BoGhQBSQ.js +107 -0
- package/dist/{ProfileRelationshipType-BRiRka6t-Ci-qvFhm.js → ProfileRelationshipType-BRiRka6t-Dezjv5jP.js} +1 -1
- package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-HoTmV1q-.js +57 -0
- package/dist/app/cli.d.ts +7 -0
- package/dist/app/cli.d.ts.map +1 -1
- package/dist/app/errors.d.ts +34 -1
- package/dist/app/errors.d.ts.map +1 -1
- package/dist/app/index.d.ts +4 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/launchers.d.ts +11 -0
- package/dist/app/launchers.d.ts.map +1 -1
- package/dist/app/operations.d.ts +2 -0
- package/dist/app/operations.d.ts.map +1 -1
- package/dist/app/runtime.d.ts +17 -2
- package/dist/app/runtime.d.ts.map +1 -1
- package/dist/app/tokens.d.ts +21 -0
- package/dist/app/tokens.d.ts.map +1 -0
- package/dist/app.js +3 -3
- package/dist/{cli-BRFsobey.js → cli-OZsH5cdL.js} +403 -76
- package/dist/cli-generator.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/utilities.d.ts.map +1 -1
- package/dist/{commands-dFz1e9A6.js → commands-UXK2F_0j.js} +35 -51
- package/dist/database-environment-D_Gk1Brf.js +20 -0
- package/dist/database-environment.d.ts +43 -0
- package/dist/database-environment.d.ts.map +1 -0
- package/dist/dist-BVjJWjT-.js +20 -0
- package/dist/{dist-EJ63xUEp.js → dist-C87Ib9Vx.js} +2 -2
- package/dist/{dist-C2BqYVu7.js → dist-DLw_E34G.js} +1654 -1748
- package/dist/{dist-Berwe80z.js → dist-GIaWaUho.js} +4 -4
- package/dist/{dist-D4BNILLN.js → dist-RSE5hCY4.js} +3 -3
- package/dist/index.js +15 -10
- package/dist/{oidcProvisioningPrimitives-D9OYu5-F-B-kYsBTM.js → oidcProvisioningPrimitives-D9OYu5-F-T9KkkDCd.js} +5 -5
- package/dist/{portability-DmQWSSXA.js → portability-CES1YC2m.js} +1 -1
- package/dist/resolveIdentity-B-PK6RwU-C9ogf8YG.js +269 -0
- package/package.json +15 -15
- package/dist/ApiKeyCollection-Bfb_nw81-B1-2YKj8.js +0 -2
- package/dist/AuditLogCollection-B1YonCAP-DP4PCCw-.js +0 -2
- package/dist/NostrIdentityCollection-Ca74dkaL-C-v57o9N.js +0 -2
- package/dist/OidcIdentityCollection-TV8VdK69-BodvOtPJ.js +0 -2
- package/dist/ProfileAssetCollection-B9hacUJa-B7PLe0ld.js +0 -2
- package/dist/ProfileLinkCollection-4uNURz32-CLY1cBnn.js +0 -2
- package/dist/ProfileMetadataCollection-DQhbfEfa-Bzv-E4ep.js +0 -2
- package/dist/ProfileMetafieldCollection-CoBuM_5G-D-VqQuY1.js +0 -2
- package/dist/ProfileRelationshipCollection-DUVgaIqf-gEkDBmkh.js +0 -2
- package/dist/ProfileRelationshipTermCollection-DtA9OM23-DI_awdol.js +0 -2
- package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-CaQMLH9f.js +0 -2
- package/dist/resolveIdentity-B-PK6RwU-BmAU1Kse.js +0 -2
package/AGENTS.md
CHANGED
|
@@ -45,6 +45,8 @@ smrt dispatch:* # Dispatch management (list/process/retry/cleanup)
|
|
|
45
45
|
smrt app <op> # App install/setup/start/migrate/worker…: agents/app-commands.md
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
DB target: config `database.url` > `DATABASE_URL` > `:memory:` (README).
|
|
49
|
+
|
|
48
50
|
File-backed SQL/TypeScript migration generation is not supported. SMRT schema
|
|
49
51
|
migrations are manifest-driven through registered objects and project manifests.
|
|
50
52
|
|
package/README.md
CHANGED
|
@@ -208,6 +208,13 @@ Run the project's package-manager install command after initialization. Existing
|
|
|
208
208
|
versions are preserved; s-m-r-t workspace projects receive `workspace:*` ranges and
|
|
209
209
|
consumer projects receive the CLI's published release line.
|
|
210
210
|
|
|
211
|
+
The generated `src/lib/server/smrt.ts` exports only `runtime` (`classOptions()`
|
|
212
|
+
and `getCollection()` over `DATABASE_URL`/`DATABASE_TYPE`), which generated API
|
|
213
|
+
routes call per request; it no longer exports the deprecated
|
|
214
|
+
`getCollection`/`getSmrtConfig` accessors. To adopt the full application
|
|
215
|
+
runtime, replace that object with `createSmrtSvelteKitRuntime()` from
|
|
216
|
+
`@happyvertical/smrt-app-runtime/sveltekit` and mount its `handle`/`init`.
|
|
217
|
+
|
|
211
218
|
### Dispatch
|
|
212
219
|
|
|
213
220
|
| Command | Description |
|
|
@@ -364,6 +371,23 @@ export default {
|
|
|
364
371
|
};
|
|
365
372
|
```
|
|
366
373
|
|
|
374
|
+
### Database precedence
|
|
375
|
+
|
|
376
|
+
Every `db:*` command resolves its database once, at startup:
|
|
377
|
+
|
|
378
|
+
1. `packages.cli.database.url` from any config layer — an explicit project
|
|
379
|
+
setting always wins, so a stray shell `DATABASE_URL` never retargets it.
|
|
380
|
+
2. `DATABASE_URL`, with the engine from the config's `database.type`, else
|
|
381
|
+
`DATABASE_TYPE` (`sqlite` | `postgres`), else the URL scheme
|
|
382
|
+
(`postgres://` / `postgresql://` → postgres, anything else sqlite). An
|
|
383
|
+
unsupported `DATABASE_TYPE` disables this step with a warning.
|
|
384
|
+
3. The `:memory:` default, which schema commands refuse.
|
|
385
|
+
|
|
386
|
+
`smrt app setup` and `smrt app migrate` hand `smrt db:migrate` the profile's
|
|
387
|
+
database through step 2, so an application does not forward
|
|
388
|
+
`DATABASE_URL` in its own config. The rule is smrt-config's
|
|
389
|
+
`resolveCliDatabaseConfig()`, which `smrt-dev-mcp` uses too.
|
|
390
|
+
|
|
367
391
|
### Entry Point Discovery
|
|
368
392
|
|
|
369
393
|
The CLI loads s-m-r-t objects from your project entry point:
|
package/agents/app-commands.md
CHANGED
|
@@ -10,9 +10,10 @@ template's scripts so an app's `package.json` scripts become one-liners.
|
|
|
10
10
|
| `scripts/smrt-app.mjs <op>` | `smrt app install\|setup\|recover\|start\|stop\|doctor\|open\|backup\|export\|import` |
|
|
11
11
|
| `scripts/smrt-prepare-migration.mjs` | `smrt app migrate` |
|
|
12
12
|
| `scripts/smrt-worker.mjs [task\|schedule]` | `smrt app worker [task\|schedule]` |
|
|
13
|
-
| `node --env-file-if-exists=.env scripts/smrt-vite.mjs <args>` | `smrt app vite <args>` (`smrt app dev …` = `vite dev …`) |
|
|
13
|
+
| `node --env-file-if-exists=.env scripts/smrt-vite.mjs <args>` | `smrt app vite <args>` (`smrt app dev …` = `vite dev …` plus `--host 127.0.0.1` / `--port $PORT` unless given: `devServerArguments`) |
|
|
14
14
|
| `smrt-mcp-apps.mjs validate-if-present && …smrt-vite.mjs build` | `smrt app build [vite args]` |
|
|
15
15
|
| `scripts/smrt-web.mjs` | `bin/smrt-web.mjs` (spawned by `start`) |
|
|
16
|
+
| — (new, #3413) | `smrt app token [create] --scopes … [--expires 30d] [--label …]`, `token list`, `token revoke <id>` |
|
|
16
17
|
| helper modules | `@happyvertical/smrt-cli/app` (side-effect-free subpath) |
|
|
17
18
|
|
|
18
19
|
## Source
|
|
@@ -30,6 +31,12 @@ config loading; `bin/smrt.js` imports `dist/` in-process so SIGTERM reaches
|
|
|
30
31
|
|
|
31
32
|
## Contracts that must not drift
|
|
32
33
|
|
|
34
|
+
- **Profile**: `resolveConfiguredRuntime` and app-runtime's SvelteKit entry
|
|
35
|
+
both call smrt-config's `resolveEffectiveApplicationRuntime(loaded)` (#3446),
|
|
36
|
+
so operator and web process agree: no `runtime` block (or no config) is
|
|
37
|
+
`local`; any present value other than `undefined` (including
|
|
38
|
+
`null`/`false`/`0`/`''`) is validated and fails closed when it is not a
|
|
39
|
+
valid block.
|
|
33
40
|
- **State root** `<XDG_STATE_HOME|~/.local/state|~/Library/Application Support|%LOCALAPPDATA%>/.<appId>-<sha256(dataRoot)[:12]>-state`,
|
|
34
41
|
mode 0700, every ancestor user/root-owned and not group/world-writable,
|
|
35
42
|
app-bound empty 0600 marker `.smrt-state-<appId>`. Shared with the running
|
|
@@ -53,9 +60,27 @@ config loading; `bin/smrt.js` imports `dist/` in-process so SIGTERM reaches
|
|
|
53
60
|
`{schemaVersion:1,status:'error',code:'operation-failed',message,recovery,secretValuesIncluded:false}`
|
|
54
61
|
plus `runtimeCode` when a `LocalRuntimeError` carries one
|
|
55
62
|
(`migration_failed` + `MIGRATION_FAILED_MESSAGE`). Messages pass through
|
|
56
|
-
`redactSecrets()` (secret-named env values, URL userinfo, `token=`, Bearer
|
|
63
|
+
`redactSecrets()` (secret-named env values, URL userinfo, `token=`, Bearer;
|
|
64
|
+
every span is found on the raw text and the union masked at once, so a
|
|
65
|
+
literal value never splits a structural match; existing `[redacted]`
|
|
66
|
+
markers are opaque, so a second pass neither corrupts nor grows text).
|
|
67
|
+
`start` sends the detached web process's stdout/stderr to a fresh 0600
|
|
68
|
+
`<state>/app.log` per start (removed, then created `wx`; a pipe would EPIPE
|
|
69
|
+
once the CLI exits); a start that never proves readiness adds `output` (the
|
|
70
|
+
last ≤ 8 KiB, redacted against the child env before the cut with
|
|
71
|
+
`{ strict: true }`: no 8-character floor for env values or Bearer tokens) and `logFile`
|
|
72
|
+
(`ApplicationStartError`).
|
|
57
73
|
The bootstrap token is written only to mode-0600 `onboarding.json` /
|
|
58
|
-
`onboarding-launch.html`; `open` passes the launch file URL, never the token.
|
|
74
|
+
`onboarding-launch.html`; `open` passes the launch file URL, never the token. `setup`/`recover` print the one-time
|
|
75
|
+
`/setup?token=` URL only to an interactive terminal (`io.operatorTerminal`,
|
|
76
|
+
stderr when both stdio are TTYs), never to stdout/stderr logs or pipes. The
|
|
77
|
+
web runtime deletes the files after a successful owner claim.
|
|
78
|
+
- **`token`** (`tokens.ts`, local only): parses every argument before touching
|
|
79
|
+
state, opens `openLocalMcpTokenStore` (validated custody, uncached
|
|
80
|
+
connection, no writer lease, so it runs beside `app:start`), and serializes
|
|
81
|
+
create/revoke under `operation.lock`. Only the `issued` document carries
|
|
82
|
+
the token (`secretValuesIncluded: true`); list/revoke/error output never
|
|
83
|
+
does.
|
|
59
84
|
- **Artifacts**: backup/export/import paths go through
|
|
60
85
|
`assertExternalArtifactPath` (real path of nearest existing ancestor, never
|
|
61
86
|
in or over the checkout). Backup refuses an existing destination; export
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-
|
|
2
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
3
|
import { SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
//#region ../profiles/dist/chunks/ApiKey-BZmbOTnL.js
|
|
@@ -127,7 +127,7 @@ var ApiKey = class extends SmrtObject {
|
|
|
127
127
|
*/
|
|
128
128
|
static async verify(key, options = {}) {
|
|
129
129
|
const keyHash = ApiKey.hashKey(key);
|
|
130
|
-
const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-
|
|
130
|
+
const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-DYAZwx0O.js").then((n) => n.n).then((n) => n.n);
|
|
131
131
|
const apiKey = await (await ApiKeyCollection.create(options)).findOne({ where: { keyHash } });
|
|
132
132
|
if (!apiKey) return null;
|
|
133
133
|
if (!apiKey.isValid()) return null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
|
+
import { t as ApiKey } from "./ApiKey-BZmbOTnL-BbZupUZ3.js";
|
|
4
|
+
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
5
|
+
//#region ../profiles/dist/chunks/ApiKeyCollection-Bfb_nw81.js
|
|
6
|
+
var ApiKeyCollection_Bfb_nw81_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
n: () => ApiKeyCollection_exports,
|
|
8
|
+
t: () => ApiKeyCollection
|
|
9
|
+
});
|
|
10
|
+
var ApiKeyCollection_exports = /* @__PURE__ */ __exportAll$1({ ApiKeyCollection: () => ApiKeyCollection });
|
|
11
|
+
var ApiKeyCollection = class extends SmrtCollection {
|
|
12
|
+
static _itemClass = ApiKey;
|
|
13
|
+
/**
|
|
14
|
+
* Find all keys for a profile
|
|
15
|
+
*/
|
|
16
|
+
async findByProfile(profileId) {
|
|
17
|
+
return await this.list({ where: { profileId } });
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find active (non-revoked, non-expired) keys for a profile
|
|
21
|
+
*/
|
|
22
|
+
async findActiveByProfile(profileId) {
|
|
23
|
+
return (await this.findByProfile(profileId)).filter((key) => key.isValid());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find key by hash
|
|
27
|
+
*/
|
|
28
|
+
async findByHash(keyHash) {
|
|
29
|
+
return await this.findOne({ where: { keyHash } });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Verify an API key string and return the record if valid
|
|
33
|
+
*/
|
|
34
|
+
async verify(key) {
|
|
35
|
+
const keyHash = ApiKey.hashKey(key);
|
|
36
|
+
const apiKey = await this.findByHash(keyHash);
|
|
37
|
+
if (!apiKey) return null;
|
|
38
|
+
if (!apiKey.isValid()) return null;
|
|
39
|
+
await apiKey.recordUsage();
|
|
40
|
+
if (!apiKey.isValid()) return null;
|
|
41
|
+
return apiKey;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a new key for a profile
|
|
45
|
+
*/
|
|
46
|
+
async generateForProfile(profile, options) {
|
|
47
|
+
return await ApiKey.generate(profile, {
|
|
48
|
+
...options,
|
|
49
|
+
db: this.options.db
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Revoke all keys for a profile
|
|
54
|
+
*/
|
|
55
|
+
async revokeAllForProfile(profileId) {
|
|
56
|
+
const keys = await this.findActiveByProfile(profileId);
|
|
57
|
+
for (const key of keys) await key.revoke();
|
|
58
|
+
return keys.length;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Revoke a specific key by prefix
|
|
62
|
+
*/
|
|
63
|
+
async revokeByPrefix(keyPrefix) {
|
|
64
|
+
const key = await this.findOne({ where: { keyPrefix } });
|
|
65
|
+
if (key?.isValid()) {
|
|
66
|
+
await key.revoke();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clean up expired keys (soft delete by setting revokedAt)
|
|
73
|
+
*/
|
|
74
|
+
async cleanupExpired() {
|
|
75
|
+
const now = /* @__PURE__ */ new Date();
|
|
76
|
+
const allKeys = await this.list({});
|
|
77
|
+
let cleaned = 0;
|
|
78
|
+
for (const key of allKeys) if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {
|
|
79
|
+
key.revokedAt = now;
|
|
80
|
+
await key.save();
|
|
81
|
+
cleaned++;
|
|
82
|
+
}
|
|
83
|
+
return cleaned;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { ApiKeyCollection_Bfb_nw81_exports as n, ApiKeyCollection as t };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { i as tenantId, r as TenantScoped } from "./dist-C87Ib9Vx.js";
|
|
3
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
4
|
+
import { SmrtCollection, SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
5
|
+
//#region ../profiles/dist/chunks/AuditLogCollection-B1YonCAP.js
|
|
6
|
+
var AuditLogCollection_B1YonCAP_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
n: () => AuditLogCollection_exports,
|
|
8
|
+
r: () => AuditLog,
|
|
9
|
+
t: () => AuditLogCollection
|
|
10
|
+
});
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
14
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
15
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
16
|
+
if (kind && result) __defProp(target, key, result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
var AuditLog = class extends SmrtObject {
|
|
20
|
+
tenantId = null;
|
|
21
|
+
profileId;
|
|
22
|
+
/**
|
|
23
|
+
* Action performed (e.g., 'issue.update', 'feedback.incorporate')
|
|
24
|
+
*/
|
|
25
|
+
action = "";
|
|
26
|
+
/**
|
|
27
|
+
* Type of resource affected (e.g., 'Issue', 'Repository')
|
|
28
|
+
*/
|
|
29
|
+
resourceType = "";
|
|
30
|
+
/**
|
|
31
|
+
* ID of the affected resource
|
|
32
|
+
*/
|
|
33
|
+
resourceId = "";
|
|
34
|
+
/**
|
|
35
|
+
* Source of the action
|
|
36
|
+
*/
|
|
37
|
+
source = "web";
|
|
38
|
+
/**
|
|
39
|
+
* Additional context (CI run ID, request ID, etc.)
|
|
40
|
+
*/
|
|
41
|
+
metadata = {};
|
|
42
|
+
onBehalfOfId;
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
super(options);
|
|
45
|
+
if (options.profileId) this.profileId = options.profileId;
|
|
46
|
+
if (options.action) this.action = options.action;
|
|
47
|
+
if (options.resourceType) this.resourceType = options.resourceType;
|
|
48
|
+
if (options.resourceId) this.resourceId = options.resourceId;
|
|
49
|
+
if (options.source) this.source = options.source;
|
|
50
|
+
if (options.metadata) this.metadata = options.metadata;
|
|
51
|
+
if (options.onBehalfOfId !== void 0) this.onBehalfOfId = options.onBehalfOfId;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the profile who performed the action
|
|
55
|
+
*/
|
|
56
|
+
async getProfile() {
|
|
57
|
+
return await this.getRelated("profileId");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the profile on whose behalf the action was performed (if any)
|
|
61
|
+
*/
|
|
62
|
+
async getOnBehalfOf() {
|
|
63
|
+
if (!this.onBehalfOfId) return null;
|
|
64
|
+
return await this.getRelated("onBehalfOfId");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the effective actor (onBehalfOf if set, otherwise profile)
|
|
68
|
+
*/
|
|
69
|
+
async getEffectiveActor() {
|
|
70
|
+
const onBehalfOf = await this.getOnBehalfOf();
|
|
71
|
+
if (onBehalfOf) return onBehalfOf;
|
|
72
|
+
return await this.getProfile();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an audit log entry
|
|
76
|
+
*/
|
|
77
|
+
static async record(options) {
|
|
78
|
+
const log = new AuditLog({
|
|
79
|
+
...options,
|
|
80
|
+
profileId: options.profile.id,
|
|
81
|
+
onBehalfOfId: options.onBehalfOf?.id
|
|
82
|
+
});
|
|
83
|
+
await log.initialize();
|
|
84
|
+
await log.save();
|
|
85
|
+
return log;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
__decorateClass([tenantId({ nullable: true })], AuditLog.prototype, "tenantId", 2);
|
|
89
|
+
__decorateClass([foreignKey("Profile", { required: true })], AuditLog.prototype, "profileId", 2);
|
|
90
|
+
__decorateClass([foreignKey("Profile", { nullable: true })], AuditLog.prototype, "onBehalfOfId", 2);
|
|
91
|
+
AuditLog = __decorateClass([TenantScoped({
|
|
92
|
+
mode: "optional",
|
|
93
|
+
allowSuperAdminBypass: true
|
|
94
|
+
}), smrt({
|
|
95
|
+
tableName: "audit_logs",
|
|
96
|
+
api: { include: ["list", "get"] },
|
|
97
|
+
mcp: { include: ["list", "get"] },
|
|
98
|
+
cli: { include: ["list"] }
|
|
99
|
+
})], AuditLog);
|
|
100
|
+
var AuditLogCollection_exports = /* @__PURE__ */ __exportAll$1({ AuditLogCollection: () => AuditLogCollection });
|
|
101
|
+
var AuditLogCollection = class extends SmrtCollection {
|
|
102
|
+
static _itemClass = AuditLog;
|
|
103
|
+
/**
|
|
104
|
+
* Find logs for a profile (actions they performed)
|
|
105
|
+
*/
|
|
106
|
+
async findByProfile(profileId) {
|
|
107
|
+
return await this.list({
|
|
108
|
+
where: { profileId },
|
|
109
|
+
orderBy: "createdAt DESC"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Find logs for a resource
|
|
114
|
+
*/
|
|
115
|
+
async findByResource(resourceType, resourceId) {
|
|
116
|
+
return await this.list({
|
|
117
|
+
where: {
|
|
118
|
+
resourceType,
|
|
119
|
+
resourceId
|
|
120
|
+
},
|
|
121
|
+
orderBy: "createdAt DESC"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Find logs by action type
|
|
126
|
+
*/
|
|
127
|
+
async findByAction(action) {
|
|
128
|
+
return await this.list({
|
|
129
|
+
where: { action },
|
|
130
|
+
orderBy: "createdAt DESC"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find logs by source
|
|
135
|
+
*/
|
|
136
|
+
async findBySource(source) {
|
|
137
|
+
return await this.list({
|
|
138
|
+
where: { source },
|
|
139
|
+
orderBy: "createdAt DESC"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Find logs where actions were performed on behalf of someone
|
|
144
|
+
*/
|
|
145
|
+
async findOnBehalfOf(profileId) {
|
|
146
|
+
return await this.list({
|
|
147
|
+
where: { onBehalfOfId: profileId },
|
|
148
|
+
orderBy: "createdAt DESC"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Record a new audit log entry
|
|
153
|
+
*/
|
|
154
|
+
async record(options) {
|
|
155
|
+
const log = new AuditLog({
|
|
156
|
+
...this.options,
|
|
157
|
+
profileId: options.profile.id,
|
|
158
|
+
action: options.action,
|
|
159
|
+
resourceType: options.resourceType,
|
|
160
|
+
resourceId: options.resourceId,
|
|
161
|
+
source: options.source || "web",
|
|
162
|
+
metadata: options.metadata || {},
|
|
163
|
+
onBehalfOfId: options.onBehalfOf?.id
|
|
164
|
+
});
|
|
165
|
+
await log.initialize();
|
|
166
|
+
await log.save();
|
|
167
|
+
return log;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get recent activity for a profile
|
|
171
|
+
*/
|
|
172
|
+
async getRecentActivity(profileId, limit = 50) {
|
|
173
|
+
return await this.list({
|
|
174
|
+
where: { profileId },
|
|
175
|
+
orderBy: "createdAt DESC",
|
|
176
|
+
limit
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get activity timeline for a resource
|
|
181
|
+
*/
|
|
182
|
+
async getResourceTimeline(resourceType, resourceId) {
|
|
183
|
+
return await this.list({
|
|
184
|
+
where: {
|
|
185
|
+
resourceType,
|
|
186
|
+
resourceId
|
|
187
|
+
},
|
|
188
|
+
orderBy: "createdAt ASC"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
export { AuditLogCollection as n, AuditLogCollection_B1YonCAP_exports as r, AuditLog as t };
|
|
@@ -4097,21 +4097,21 @@ var NostrIdentity = class extends SmrtObject {
|
|
|
4097
4097
|
* Find identity by public key
|
|
4098
4098
|
*/
|
|
4099
4099
|
static async findByPubkey(pubkey, options = {}) {
|
|
4100
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4100
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4101
4101
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { pubkey } });
|
|
4102
4102
|
}
|
|
4103
4103
|
/**
|
|
4104
4104
|
* Find identity by email
|
|
4105
4105
|
*/
|
|
4106
4106
|
static async findByEmail(email, options = {}) {
|
|
4107
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4107
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4108
4108
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { email: email.toLowerCase() } });
|
|
4109
4109
|
}
|
|
4110
4110
|
/**
|
|
4111
4111
|
* Find identity by NIP-05 username
|
|
4112
4112
|
*/
|
|
4113
4113
|
static async findByNip05(username, options = {}) {
|
|
4114
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4114
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4115
4115
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { nip05Username: username.toLowerCase() } });
|
|
4116
4116
|
}
|
|
4117
4117
|
/**
|
|
@@ -4186,4 +4186,4 @@ NostrIdentity = __decorateClass([smrt({
|
|
|
4186
4186
|
cli: { include: ["list", "get"] }
|
|
4187
4187
|
})], NostrIdentity);
|
|
4188
4188
|
//#endregion
|
|
4189
|
-
export {
|
|
4189
|
+
export { deriveEncryptionKey as a, privkeyToNsec as c, verifyNostrSignature as d, decryptPrivkey as i, pubkeyToNpub as l, __exportAll as n, encryptPrivkey as o, computeEventId as r, generateNostrKeypair as s, NostrIdentity as t, verifyAuthEvent as u };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { n as __exportAll$1, o as encryptPrivkey, s as generateNostrKeypair, t as NostrIdentity } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
|
+
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
4
|
+
//#region ../profiles/dist/chunks/NostrIdentityCollection-Ca74dkaL.js
|
|
5
|
+
var NostrIdentityCollection_Ca74dkaL_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
n: () => NostrIdentityCollection_exports,
|
|
7
|
+
t: () => NostrIdentityCollection
|
|
8
|
+
});
|
|
9
|
+
var NostrIdentityCollection_exports = /* @__PURE__ */ __exportAll$1({ NostrIdentityCollection: () => NostrIdentityCollection });
|
|
10
|
+
var NostrIdentityCollection = class extends SmrtCollection {
|
|
11
|
+
static _itemClass = NostrIdentity;
|
|
12
|
+
/**
|
|
13
|
+
* Find identities for a profile
|
|
14
|
+
*/
|
|
15
|
+
async findByProfile(profileId) {
|
|
16
|
+
return await this.list({ where: { profileId } });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Find identity by public key
|
|
20
|
+
*/
|
|
21
|
+
async findByPubkey(pubkey) {
|
|
22
|
+
return await this.findOne({ where: { pubkey } });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Find identity by email
|
|
26
|
+
*/
|
|
27
|
+
async findByEmail(email) {
|
|
28
|
+
return await this.findOne({ where: { email: email.toLowerCase() } });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Find identity by NIP-05 username
|
|
32
|
+
*/
|
|
33
|
+
async findByNip05(username) {
|
|
34
|
+
return await this.findOne({ where: { nip05Username: username.toLowerCase() } });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Link a new Nostr identity to a profile with a new keypair
|
|
38
|
+
* @param profile - The profile to link to
|
|
39
|
+
* @param email - Email address for this identity
|
|
40
|
+
* @param masterSecret - Server master secret for encryption
|
|
41
|
+
* @param nip05Username - Optional NIP-05 username (defaults to email local part)
|
|
42
|
+
*/
|
|
43
|
+
async createForProfile(profile, email, masterSecret, nip05Username) {
|
|
44
|
+
const normalizedEmail = email.toLowerCase();
|
|
45
|
+
if (await this.findByEmail(normalizedEmail)) throw new Error(`Nostr identity already exists for email: ${email}`);
|
|
46
|
+
const keypair = generateNostrKeypair();
|
|
47
|
+
const encrypted = encryptPrivkey(keypair.privkey, masterSecret);
|
|
48
|
+
const username = nip05Username?.toLowerCase() || normalizedEmail.split("@")[0];
|
|
49
|
+
const identity = new NostrIdentity({
|
|
50
|
+
...this.options,
|
|
51
|
+
profileId: profile.id,
|
|
52
|
+
pubkey: keypair.pubkey,
|
|
53
|
+
encryptedPrivkey: encrypted.ciphertext,
|
|
54
|
+
encryptionIv: encrypted.iv,
|
|
55
|
+
encryptionTag: encrypted.tag,
|
|
56
|
+
email: normalizedEmail,
|
|
57
|
+
nip05Username: username
|
|
58
|
+
});
|
|
59
|
+
await identity.initialize();
|
|
60
|
+
await identity.save();
|
|
61
|
+
return identity;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Link an existing Nostr identity (with encrypted keypair) to a profile
|
|
65
|
+
*/
|
|
66
|
+
async linkToProfile(profile, nostrData) {
|
|
67
|
+
if (await this.findByPubkey(nostrData.pubkey)) throw new Error("Nostr identity with this public key already exists");
|
|
68
|
+
if (await this.findByEmail(nostrData.email)) throw new Error("Nostr identity with this email already exists");
|
|
69
|
+
const identity = new NostrIdentity({
|
|
70
|
+
...this.options,
|
|
71
|
+
profileId: profile.id,
|
|
72
|
+
pubkey: nostrData.pubkey,
|
|
73
|
+
encryptedPrivkey: nostrData.encryptedPrivkey,
|
|
74
|
+
encryptionIv: nostrData.encryptionIv,
|
|
75
|
+
encryptionTag: nostrData.encryptionTag,
|
|
76
|
+
email: nostrData.email.toLowerCase(),
|
|
77
|
+
nip05Username: nostrData.nip05Username?.toLowerCase() || nostrData.email.toLowerCase().split("@")[0]
|
|
78
|
+
});
|
|
79
|
+
await identity.initialize();
|
|
80
|
+
await identity.save();
|
|
81
|
+
return identity;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Unlink a Nostr identity from a profile
|
|
85
|
+
*/
|
|
86
|
+
async unlinkFromProfile(profileId, pubkey) {
|
|
87
|
+
const identity = await this.findOne({ where: {
|
|
88
|
+
profileId,
|
|
89
|
+
pubkey
|
|
90
|
+
} });
|
|
91
|
+
if (identity) {
|
|
92
|
+
await identity.delete();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get NIP-05 response data for the /.well-known/nostr.json endpoint
|
|
99
|
+
* @param username - Optional username filter, returns all if not provided
|
|
100
|
+
*/
|
|
101
|
+
async getNip05Response(username) {
|
|
102
|
+
const where = username ? { nip05Username: username.toLowerCase() } : {};
|
|
103
|
+
const identities = await this.list({ where });
|
|
104
|
+
const names = {};
|
|
105
|
+
for (const identity of identities) if (identity.nip05Username && identity.pubkey) names[identity.nip05Username] = identity.pubkey;
|
|
106
|
+
return { names };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if a NIP-05 username is available
|
|
110
|
+
*/
|
|
111
|
+
async isNip05Available(username) {
|
|
112
|
+
return await this.findByNip05(username) === null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Update NIP-05 username for an identity
|
|
116
|
+
*/
|
|
117
|
+
async updateNip05Username(identity, newUsername) {
|
|
118
|
+
const normalizedUsername = newUsername.toLowerCase();
|
|
119
|
+
const existing = await this.findByNip05(normalizedUsername);
|
|
120
|
+
if (existing && existing.id !== identity.id) throw new Error(`NIP-05 username "${newUsername}" is already taken`);
|
|
121
|
+
identity.nip05Username = normalizedUsername;
|
|
122
|
+
await identity.save();
|
|
123
|
+
return identity;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
//#endregion
|
|
127
|
+
export { NostrIdentityCollection_Ca74dkaL_exports as n, NostrIdentityCollection as t };
|
|
@@ -35,7 +35,7 @@ var OidcIdentity = class extends SmrtObject {
|
|
|
35
35
|
* Find identity by issuer and subject
|
|
36
36
|
*/
|
|
37
37
|
static async findBySubject(issuer, subject, options = {}) {
|
|
38
|
-
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-TV8VdK69-
|
|
38
|
+
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-TV8VdK69-Cv1ZTeuk.js").then((n) => n.r).then((n) => n.r);
|
|
39
39
|
return await (await OidcIdentityCollection.create(options)).findBySubject(issuer, subject);
|
|
40
40
|
}
|
|
41
41
|
/** Build the collision-free natural key for one OIDC issuer subject. */
|
|
@@ -58,7 +58,7 @@ var OidcIdentity = class extends SmrtObject {
|
|
|
58
58
|
static async findOrCreate(profile, oidcData, options = {}) {
|
|
59
59
|
const profileId = profile.id;
|
|
60
60
|
if (typeof profileId !== "string" || !profileId) throw new Error("OidcIdentity.findOrCreate() requires a saved Profile.");
|
|
61
|
-
const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-B-PK6RwU-
|
|
61
|
+
const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-B-PK6RwU-C9ogf8YG.js").then((n) => n.i).then((n) => n.i);
|
|
62
62
|
const profileOptions = profile.options ?? {};
|
|
63
63
|
return (await reuseExistingOidcIdentityForProfile(profileId, {
|
|
64
64
|
email: oidcData.email,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
|
+
import { t as OidcIdentity } from "./OidcIdentity-Cb1bxreM-LXmC7uAf.js";
|
|
4
|
+
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
5
|
+
//#region ../profiles/dist/chunks/OidcIdentityCollection-TV8VdK69.js
|
|
6
|
+
var OidcIdentityCollection_TV8VdK69_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
n: () => OidcIdentityCollection,
|
|
8
|
+
r: () => OidcIdentityCollection_exports,
|
|
9
|
+
t: () => AmbiguousOidcIdentityError
|
|
10
|
+
});
|
|
11
|
+
var OidcIdentityCollection_exports = /* @__PURE__ */ __exportAll$1({
|
|
12
|
+
AmbiguousOidcIdentityError: () => AmbiguousOidcIdentityError,
|
|
13
|
+
OidcIdentityCollection: () => OidcIdentityCollection
|
|
14
|
+
});
|
|
15
|
+
var AmbiguousOidcIdentityError = class extends Error {
|
|
16
|
+
constructor(issuer, subject) {
|
|
17
|
+
super("Multiple OIDC identities match the same issuer and subject.");
|
|
18
|
+
this.issuer = issuer;
|
|
19
|
+
this.subject = subject;
|
|
20
|
+
this.name = "AmbiguousOidcIdentityError";
|
|
21
|
+
}
|
|
22
|
+
issuer;
|
|
23
|
+
subject;
|
|
24
|
+
};
|
|
25
|
+
var OidcIdentityCollection = class extends SmrtCollection {
|
|
26
|
+
static _itemClass = OidcIdentity;
|
|
27
|
+
/**
|
|
28
|
+
* Find identities for a profile
|
|
29
|
+
*/
|
|
30
|
+
async findByProfile(profileId) {
|
|
31
|
+
return await this.list({ where: { profileId } });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Find identity by issuer and subject
|
|
35
|
+
*/
|
|
36
|
+
async findBySubject(issuer, subject) {
|
|
37
|
+
const result = await this.db.query(`SELECT CAST(id AS VARCHAR) AS id,
|
|
38
|
+
CAST(profile_id AS VARCHAR) AS profile_id
|
|
39
|
+
FROM oidc_identities
|
|
40
|
+
WHERE issuer = ? AND subject = ?
|
|
41
|
+
LIMIT 2`, issuer, subject);
|
|
42
|
+
if (result.rows.length > 1) throw new AmbiguousOidcIdentityError(issuer, subject);
|
|
43
|
+
const row = result.rows[0];
|
|
44
|
+
const id = row?.id;
|
|
45
|
+
const profileId = row?.profile_id;
|
|
46
|
+
if (typeof id !== "string" || typeof profileId !== "string") return null;
|
|
47
|
+
const identity = await this.get({ id });
|
|
48
|
+
if (!identity) return null;
|
|
49
|
+
identity.id = id;
|
|
50
|
+
identity.profileId = profileId;
|
|
51
|
+
return identity;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find identities by provider
|
|
55
|
+
*/
|
|
56
|
+
async findByProvider(provider) {
|
|
57
|
+
return await this.list({ where: { provider } });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reuse an existing exact OIDC identity for its unchanged Profile.
|
|
61
|
+
*
|
|
62
|
+
* @deprecated New authentication links require the owner-aware,
|
|
63
|
+
* transactional provisioning APIs. This compatibility helper may refresh a
|
|
64
|
+
* legacy Profile type, but refuses to create or rebind authority.
|
|
65
|
+
*/
|
|
66
|
+
async linkToProfile(profile, oidcData) {
|
|
67
|
+
return OidcIdentity.findOrCreate(profile, oidcData, this.options);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Unlink an OIDC identity from a profile
|
|
71
|
+
*/
|
|
72
|
+
async unlinkFromProfile(profileId, issuer, subject) {
|
|
73
|
+
const identity = await this.findOne({ where: {
|
|
74
|
+
profileId,
|
|
75
|
+
issuer,
|
|
76
|
+
subject
|
|
77
|
+
} });
|
|
78
|
+
if (identity) {
|
|
79
|
+
await identity.delete();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
export { OidcIdentityCollection as n, OidcIdentityCollection_TV8VdK69_exports as r, AmbiguousOidcIdentityError as t };
|