@happyvertical/smrt-app-runtime 0.57.7 → 0.57.8

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 CHANGED
@@ -232,7 +232,7 @@ Diagnostics authorize (owner role or `runtime_diagnostics.read` on an active,
232
232
  session-matching membership) before reading the runtime and return only stable
233
233
  `{ schemaVersion: 1, error: { code } }` failures. Owner setup is local-only and
234
234
  re-checks on every request that both the peer address and the URL host are
235
- loopback; its `default` action reads `token`, `name`, `email`, and optional
235
+ loopback; its `default` action reads `token`, `name`, `email`, and optional `password` and
236
236
  `tenantName`, sets the session cookie, and redirects 303, or returns
237
237
  `fail(status, { code, message })` with `setup_disabled` (404),
238
238
  `setup_unavailable` (403), `setup_invalid_input` (400), `setup_invalid`
@@ -243,6 +243,27 @@ runtime removes the `smrt app setup` / `recover` hand-off files
243
243
  invitation; failed claims leave them untouched. `onOwnerClaimed` remains for
244
244
  extra app cleanup and `removeOnboardingHandoff: false` opts out.
245
245
 
246
+ Password-based applications should pass `{ requirePassword: true }` to
247
+ `createOwnerSetupPage` and include a password field in their setup form. The
248
+ password is stored in the owner-claim transaction; a rejected password leaves
249
+ the token available for retry. Existing bootstrap consumers may omit it.
250
+ Use one server-only `PasswordPolicyOptions` value for bootstrap and sign-in,
251
+ including any pepper, length constraints, or custom rejection hook:
252
+
253
+ ```typescript
254
+ const passwordPolicy = { pepper: serverSecrets.passwordPepper, minLength: 16 };
255
+ const runtime = createSmrtSvelteKitRuntime({ passwordPolicy });
256
+ const passwords = await PasswordCredentialService.create({
257
+ ...runtime.classOptions('User'),
258
+ password: passwordPolicy,
259
+ });
260
+ ```
261
+
262
+ `initializeLocalApplicationRuntime` accepts the same `passwordPolicy` option.
263
+ It is never taken from form data or included in diagnostics. Bootstrap uses the
264
+ policy on its transaction-bound service, and registers password-management
265
+ permissions before seeding the owner's grants.
266
+
246
267
  `resolveApplicationId()` and `runtimeConfigurationFingerprint()` (root entry)
247
268
  are the canonical app ID and secret-free configuration fingerprint shared by
248
269
  the web health route and process managers.
@@ -9,7 +9,7 @@ import { resolveApplicationRuntime } from "@happyvertical/smrt-config";
9
9
  import { SCHEDULED_JOB_QUEUE, ScheduleRunner, TaskRunner } from "@happyvertical/smrt-jobs";
10
10
  import { Person, ProfileTypeCollection } from "@happyvertical/smrt-profiles";
11
11
  import { withSystemContext } from "@happyvertical/smrt-tenancy";
12
- import { DEFAULT_ROLE_SLUGS, MembershipCollection, MembershipStatus, PermissionResolver, RoleCollection, SessionCollection, SessionService, TenantCollection, TenantService, TenantStatus, UserCollection, UserStatus } from "@happyvertical/smrt-users";
12
+ import { DEFAULT_ROLE_SLUGS, MembershipCollection, MembershipStatus, PasswordCredentialService, PermissionResolver, RoleCollection, SessionCollection, SessionService, TenantCollection, TenantService, TenantStatus, UserCollection, UserStatus, ensurePasswordPermissionsRegistered } from "@happyvertical/smrt-users";
13
13
  import { getDatabase } from "@happyvertical/sql";
14
14
  import { AsyncLocalStorage } from "node:async_hooks";
15
15
  import { chmodSync, closeSync, constants as constants$1, existsSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
@@ -1725,6 +1725,7 @@ async function initializeLocalApplicationRuntime(options) {
1725
1725
  backgroundJobs: options.backgroundJobs === true,
1726
1726
  paidCapabilities: options.paidCapabilities === true,
1727
1727
  ownerTenant,
1728
+ passwordPolicy: options.passwordPolicy,
1728
1729
  now: options.now ?? (() => /* @__PURE__ */ new Date()),
1729
1730
  canonicalSourceRoot
1730
1731
  });
@@ -1766,6 +1767,7 @@ var InitializedLocalApplicationRuntime = class {
1766
1767
  bootstrapTtlSeconds;
1767
1768
  sessionTtlSeconds;
1768
1769
  ownerTenant;
1770
+ passwordPolicy;
1769
1771
  now;
1770
1772
  canonicalSourceRoot;
1771
1773
  constructor(state) {
@@ -1776,6 +1778,7 @@ var InitializedLocalApplicationRuntime = class {
1776
1778
  this.bootstrapTtlSeconds = state.bootstrapTtlSeconds;
1777
1779
  this.sessionTtlSeconds = state.sessionTtlSeconds;
1778
1780
  this.ownerTenant = state.ownerTenant;
1781
+ this.passwordPolicy = state.passwordPolicy;
1779
1782
  this.backgroundJobsEnabled = state.backgroundJobs;
1780
1783
  this.paidCapabilitiesEnabled = state.paidCapabilities;
1781
1784
  this.now = state.now;
@@ -1818,6 +1821,7 @@ var InitializedLocalApplicationRuntime = class {
1818
1821
  const now = this.now();
1819
1822
  const nowIso = now.toISOString();
1820
1823
  if (!await isBootstrapTokenClaimable(this.db, tokenHash, nowIso)) await throwBootstrapClaimFailure(this.db, now);
1824
+ if (input.password) ensurePasswordPermissionsRegistered();
1821
1825
  await (await RoleCollection.create({ db: this.db })).seedSystemRoles({ seedPermissions: true });
1822
1826
  return this.db.transaction(async (tx) => {
1823
1827
  if ((await tx.query(`UPDATE ${BOOTSTRAP_TABLE}
@@ -1828,7 +1832,7 @@ var InitializedLocalApplicationRuntime = class {
1828
1832
  AND expires_at > ?
1829
1833
  RETURNING expires_at`, nowIso, tokenHash, nowIso)).rows.length === 0) await throwBootstrapClaimFailure(tx, now);
1830
1834
  if (await findExistingOwner(tx)) throw new LocalRuntimeError("bootstrap_claimed", "The local owner has already been claimed.");
1831
- return createLocalOwnerRecords(tx, input, this.sessionTtlSeconds, this.ownerTenant);
1835
+ return createLocalOwnerRecords(tx, input, this.sessionTtlSeconds, this.ownerTenant, this.passwordPolicy);
1832
1836
  });
1833
1837
  }
1834
1838
  /** Restore the normal server-side session created by owner bootstrap. */
@@ -1918,7 +1922,7 @@ var InitializedLocalApplicationRuntime = class {
1918
1922
  return createHmac("sha256", (await validateApplicationSecret(this.paths.applicationSecret, this.canonicalSourceRoot)).trim()).update(token).digest("hex");
1919
1923
  }
1920
1924
  };
1921
- async function createLocalOwnerRecords(db, input, sessionTtlSeconds, ownerTenant) {
1925
+ async function createLocalOwnerRecords(db, input, sessionTtlSeconds, ownerTenant, passwordPolicy) {
1922
1926
  const existingTenant = await (await TenantCollection.create({ db })).findBySlug(ownerTenant.slug);
1923
1927
  const memberships = await MembershipCollection.create({ db });
1924
1928
  if (existingTenant) await assertAdoptableOwnerTenant(existingTenant, ownerTenant, memberships);
@@ -1972,7 +1976,25 @@ async function createLocalOwnerRecords(db, input, sessionTtlSeconds, ownerTenant
1972
1976
  ttl: sessionTtlSeconds,
1973
1977
  userAgent: input.userAgent,
1974
1978
  ipAddress: input.ipAddress,
1975
- data: { localOwner: true }
1979
+ data: { localOwner: true },
1980
+ authMethod: input.password ? "password" : "local-bootstrap"
1981
+ });
1982
+ if (input.password) await (await PasswordCredentialService.create({
1983
+ db,
1984
+ sessionTtl: sessionTtlSeconds,
1985
+ password: passwordPolicy
1986
+ })).setPassword({
1987
+ actor: {
1988
+ user,
1989
+ membership,
1990
+ permissions: [],
1991
+ tenantId: requireId(tenant.id, "Tenant"),
1992
+ sessionId: requireId(session.id, "Session"),
1993
+ authMethod: "password",
1994
+ parent: null
1995
+ },
1996
+ userId,
1997
+ password: input.password
1976
1998
  });
1977
1999
  return Object.freeze({
1978
2000
  profileId: requireId(person.id, "Person"),
@@ -2750,4 +2772,4 @@ function isDirectoryNotEmpty(error) {
2750
2772
  //#endregion
2751
2773
  export { reclaimStaleRecord as A, deepFrozenPlainCopy as B, findInstalledPackage as C, processExists as D, lockOwnerPid as E, LOCAL_MCP_TOKEN_PRINCIPAL as F, initializeDeployedApplicationRuntime as G, resolveBoundMembershipPermissions as H, LOCAL_MCP_TOKEN_TABLE as I, isSessionTenantAuthorized as K, MAX_LOCAL_MCP_TOKEN_TTL_SECONDS as L, runtimeConfigurationFingerprint as M, DEFAULT_LOCAL_MCP_TOKEN_TTL_SECONDS as N, withOperationLock as O, LOCAL_MCP_TOKEN_PREFIX as P, isLocalMcpTokenPrincipal as R, createProviderReadinessProbe as S, isStaleOperationLock as T, DeployedRuntimeCleanupError as U, ownValue as V, DeployedRuntimeError as W, MAX_RUNTIME_DIAGNOSTIC_TOOLS as _, openLocalMcpTokenStore as a, projectRuntimeDiagnostics as b, validateApplicationId as c, readActiveWriterLease as d, ONBOARDING_HANDOFF_FILES as f, MAX_RUNTIME_DIAGNOSTIC_ERRORS as g, resolveApplicationStateRoot as h, initializeLocalApplicationRuntime as i, resolveApplicationId as j, RECLAIM_MUTEX_FILE as k, validateLocalDatabaseStorage as l, removeOnboardingHandoff as m, MIGRATION_FAILED_MESSAGE as n, prepareLocalDatabaseStorage as o, prepareApplicationStateRoot as p, encodeApplicationId as r, resolveLocalRuntimePaths as s, LocalRuntimeError as t, acquireWriterLease as u, RUNTIME_DIAGNOSTICS_SCHEMA_VERSION as v, resolveReadinessModule as w, PROVIDER_READINESS_SETTINGS as x, RUNTIME_WORKER_FRESHNESS_SECONDS as y, PlainDataError as z };
2752
2774
 
2753
- //# sourceMappingURL=src-DeNO6bG_.js.map
2775
+ //# sourceMappingURL=src-C98Cqurb.js.map