@objectstack/plugin-auth 7.8.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -403,6 +403,57 @@ declare class AuthManager {
403
403
  getDataEngine(): IDataEngine | undefined;
404
404
  }
405
405
 
406
+ /**
407
+ * Shared `set-initial-password` handler.
408
+ *
409
+ * better-auth ships a `setPassword` operation that does EXACTLY what we want
410
+ * (require a session, enforce min/max length, link a `credential` account if
411
+ * none exists, refuse if one already does). But it is registered with
412
+ * `createAuthEndpoint({ ... })` — note: NO leading path string — which means
413
+ * better-auth deliberately exposes it as a **server-only** `auth.api.setPassword`
414
+ * call and gives it no HTTP route. Setting a password without proving the old
415
+ * one is privilege-sensitive, so it must not be reachable over the wire by
416
+ * default.
417
+ *
418
+ * To let an SSO-onboarded user set an *initial* local password from the
419
+ * browser, we wrap that server API in our own authenticated HTTP route. This
420
+ * helper is the single source of truth for that route body so the two mount
421
+ * points — the full `AuthPlugin` (host kernel) and the cloud `AuthProxyPlugin`
422
+ * (per-environment runtime) — stay in lockstep instead of hand-copying ~50
423
+ * lines of hash/createAccount logic (the original drift that let #1544 ship a
424
+ * route on one path but not the other).
425
+ */
426
+ /** Minimal shape of the better-auth server API we depend on. */
427
+ interface SetPasswordCapableApi {
428
+ setPassword(opts: {
429
+ body: {
430
+ newPassword: string;
431
+ };
432
+ headers: Headers;
433
+ }): Promise<unknown>;
434
+ }
435
+ interface SetInitialPasswordResult {
436
+ /** HTTP status to return to the caller. */
437
+ status: number;
438
+ /** JSON body; mirrors the `{ success, error: { code, message } }` envelope the client parses. */
439
+ body: {
440
+ success: boolean;
441
+ error?: {
442
+ code: string;
443
+ message: string;
444
+ };
445
+ };
446
+ }
447
+ /**
448
+ * Run set-initial-password against the environment's better-auth API.
449
+ *
450
+ * @param authApi the better-auth server api (`auth.api`, via `AuthManager.getApi()`)
451
+ * @param request the raw Web `Request` — its `headers` carry the session
452
+ * cookie that better-auth's session middleware reads, and its
453
+ * body carries `{ newPassword }`.
454
+ */
455
+ declare function runSetInitialPassword(authApi: SetPasswordCapableApi, request: Request): Promise<SetInitialPasswordResult>;
456
+
406
457
  /**
407
458
  * Mapping from better-auth model names to ObjectStack protocol object names.
408
459
  *
@@ -1172,4 +1223,4 @@ declare function buildDeviceAuthorizationPluginSchema(): {
1172
1223
  };
1173
1224
  };
1174
1225
 
1175
- export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName };
1226
+ export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, type SetInitialPasswordResult, type SetPasswordCapableApi, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName, runSetInitialPassword };
package/dist/index.d.ts CHANGED
@@ -403,6 +403,57 @@ declare class AuthManager {
403
403
  getDataEngine(): IDataEngine | undefined;
404
404
  }
405
405
 
406
+ /**
407
+ * Shared `set-initial-password` handler.
408
+ *
409
+ * better-auth ships a `setPassword` operation that does EXACTLY what we want
410
+ * (require a session, enforce min/max length, link a `credential` account if
411
+ * none exists, refuse if one already does). But it is registered with
412
+ * `createAuthEndpoint({ ... })` — note: NO leading path string — which means
413
+ * better-auth deliberately exposes it as a **server-only** `auth.api.setPassword`
414
+ * call and gives it no HTTP route. Setting a password without proving the old
415
+ * one is privilege-sensitive, so it must not be reachable over the wire by
416
+ * default.
417
+ *
418
+ * To let an SSO-onboarded user set an *initial* local password from the
419
+ * browser, we wrap that server API in our own authenticated HTTP route. This
420
+ * helper is the single source of truth for that route body so the two mount
421
+ * points — the full `AuthPlugin` (host kernel) and the cloud `AuthProxyPlugin`
422
+ * (per-environment runtime) — stay in lockstep instead of hand-copying ~50
423
+ * lines of hash/createAccount logic (the original drift that let #1544 ship a
424
+ * route on one path but not the other).
425
+ */
426
+ /** Minimal shape of the better-auth server API we depend on. */
427
+ interface SetPasswordCapableApi {
428
+ setPassword(opts: {
429
+ body: {
430
+ newPassword: string;
431
+ };
432
+ headers: Headers;
433
+ }): Promise<unknown>;
434
+ }
435
+ interface SetInitialPasswordResult {
436
+ /** HTTP status to return to the caller. */
437
+ status: number;
438
+ /** JSON body; mirrors the `{ success, error: { code, message } }` envelope the client parses. */
439
+ body: {
440
+ success: boolean;
441
+ error?: {
442
+ code: string;
443
+ message: string;
444
+ };
445
+ };
446
+ }
447
+ /**
448
+ * Run set-initial-password against the environment's better-auth API.
449
+ *
450
+ * @param authApi the better-auth server api (`auth.api`, via `AuthManager.getApi()`)
451
+ * @param request the raw Web `Request` — its `headers` carry the session
452
+ * cookie that better-auth's session middleware reads, and its
453
+ * body carries `{ newPassword }`.
454
+ */
455
+ declare function runSetInitialPassword(authApi: SetPasswordCapableApi, request: Request): Promise<SetInitialPasswordResult>;
456
+
406
457
  /**
407
458
  * Mapping from better-auth model names to ObjectStack protocol object names.
408
459
  *
@@ -1172,4 +1223,4 @@ declare function buildDeviceAuthorizationPluginSchema(): {
1172
1223
  };
1173
1224
  };
1174
1225
 
1175
- export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName };
1226
+ export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, type SetInitialPasswordResult, type SetPasswordCapableApi, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName, runSetInitialPassword };
package/dist/index.js CHANGED
@@ -63,7 +63,8 @@ __export(index_exports, {
63
63
  buildTwoFactorPluginSchema: () => buildTwoFactorPluginSchema,
64
64
  createObjectQLAdapter: () => createObjectQLAdapter,
65
65
  createObjectQLAdapterFactory: () => createObjectQLAdapterFactory,
66
- resolveProtocolName: () => resolveProtocolName
66
+ resolveProtocolName: () => resolveProtocolName,
67
+ runSetInitialPassword: () => runSetInitialPassword
67
68
  });
68
69
  module.exports = __toCommonJS(index_exports);
69
70
 
@@ -1191,14 +1192,17 @@ var AuthManager = class {
1191
1192
  */
1192
1193
  generateSecret() {
1193
1194
  const envSecret = (0, import_types.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]);
1194
- if (!envSecret) {
1195
- const fallbackSecret = "dev-secret-" + Date.now();
1196
- console.warn(
1197
- "\u26A0\uFE0F WARNING: No OS_AUTH_SECRET environment variable set! Using a temporary development secret. This is NOT secure for production use. Please set OS_AUTH_SECRET in your environment variables."
1195
+ if (envSecret) return envSecret;
1196
+ if (process.env.NODE_ENV === "production") {
1197
+ throw new Error(
1198
+ "[auth] OS_AUTH_SECRET is required in production but is not set. Refusing to boot with a temporary development secret \u2014 session tokens would be forgeable. Set OS_AUTH_SECRET to a strong random value."
1198
1199
  );
1199
- return fallbackSecret;
1200
1200
  }
1201
- return envSecret;
1201
+ const fallbackSecret = "dev-secret-" + Date.now();
1202
+ console.warn(
1203
+ "\u26A0\uFE0F WARNING: No OS_AUTH_SECRET environment variable set! Using a temporary development secret. This is NOT secure for production use. Please set OS_AUTH_SECRET in your environment variables."
1204
+ );
1205
+ return fallbackSecret;
1202
1206
  }
1203
1207
  /**
1204
1208
  * Update the base URL at runtime.
@@ -1404,6 +1408,37 @@ var AuthManager = class {
1404
1408
  }
1405
1409
  };
1406
1410
 
1411
+ // src/set-initial-password.ts
1412
+ async function runSetInitialPassword(authApi, request) {
1413
+ let parsed;
1414
+ try {
1415
+ parsed = await request.json();
1416
+ } catch {
1417
+ parsed = {};
1418
+ }
1419
+ const newPassword = parsed?.newPassword;
1420
+ if (typeof newPassword !== "string" || newPassword.length === 0) {
1421
+ return {
1422
+ status: 400,
1423
+ body: { success: false, error: { code: "invalid_request", message: "newPassword is required" } }
1424
+ };
1425
+ }
1426
+ try {
1427
+ await authApi.setPassword({ body: { newPassword }, headers: request.headers });
1428
+ return { status: 200, body: { success: true } };
1429
+ } catch (error) {
1430
+ return mapSetPasswordError(error);
1431
+ }
1432
+ }
1433
+ function mapSetPasswordError(error) {
1434
+ const e = error;
1435
+ const code = e?.body?.code ?? "internal";
1436
+ const message = e?.body?.message ?? e?.message ?? "set-initial-password failed";
1437
+ const rawStatus = typeof e?.statusCode === "number" ? e.statusCode : typeof e?.status === "number" ? e.status : 500;
1438
+ const status = code === "PASSWORD_ALREADY_SET" ? 409 : rawStatus;
1439
+ return { status, body: { success: false, error: { code, message } } };
1440
+ }
1441
+
1407
1442
  // src/manifest.ts
1408
1443
  var import_identity = require("@objectstack/platform-objects/identity");
1409
1444
  var AUTH_PLUGIN_ID = "com.objectstack.plugin-auth";
@@ -1701,53 +1736,9 @@ var AuthPlugin = class {
1701
1736
  });
1702
1737
  rawApp.post(`${basePath}/set-initial-password`, async (c) => {
1703
1738
  try {
1704
- let body = {};
1705
- try {
1706
- body = await c.req.json();
1707
- } catch {
1708
- body = {};
1709
- }
1710
- const newPassword = body?.newPassword;
1711
- if (typeof newPassword !== "string" || newPassword.length === 0) {
1712
- return c.json({ success: false, error: { code: "invalid_request", message: "newPassword is required" } }, 400);
1713
- }
1714
1739
  const authApi = await this.authManager.getApi();
1715
- const session = await authApi.getSession({ headers: c.req.raw.headers });
1716
- if (!session?.user?.id) {
1717
- return c.json({ success: false, error: { code: "unauthorized", message: "Sign in first" } }, 401);
1718
- }
1719
- const userId = session.user.id;
1720
- const authCtx = await this.authManager.getAuthContext();
1721
- if (!authCtx?.internalAdapter || !authCtx?.password) {
1722
- return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
1723
- }
1724
- const minLen = authCtx.password?.config?.minPasswordLength ?? 8;
1725
- const maxLen = authCtx.password?.config?.maxPasswordLength ?? 128;
1726
- if (newPassword.length < minLen) {
1727
- return c.json({ success: false, error: { code: "password_too_short", message: `Password must be at least ${minLen} characters` } }, 400);
1728
- }
1729
- if (newPassword.length > maxLen) {
1730
- return c.json({ success: false, error: { code: "password_too_long", message: `Password must be at most ${maxLen} characters` } }, 400);
1731
- }
1732
- const accounts = await authCtx.internalAdapter.findAccounts(userId);
1733
- const existingCredential = accounts?.find?.((a) => a.providerId === "credential" && a.password);
1734
- if (existingCredential) {
1735
- return c.json({
1736
- success: false,
1737
- error: {
1738
- code: "credential_account_exists",
1739
- message: "A local password is already set for this account. Use change-password instead."
1740
- }
1741
- }, 409);
1742
- }
1743
- const passwordHash = await authCtx.password.hash(newPassword);
1744
- await authCtx.internalAdapter.createAccount({
1745
- userId,
1746
- providerId: "credential",
1747
- accountId: userId,
1748
- password: passwordHash
1749
- });
1750
- return c.json({ success: true });
1740
+ const { status, body } = await runSetInitialPassword(authApi, c.req.raw);
1741
+ return c.json(body, status);
1751
1742
  } catch (error) {
1752
1743
  const err = error instanceof Error ? error : new Error(String(error));
1753
1744
  ctx.logger.error("[AuthPlugin] set-initial-password failed", err);
@@ -1973,6 +1964,7 @@ var AuthPlugin = class {
1973
1964
  buildTwoFactorPluginSchema,
1974
1965
  createObjectQLAdapter,
1975
1966
  createObjectQLAdapterFactory,
1976
- resolveProtocolName
1967
+ resolveProtocolName,
1968
+ runSetInitialPassword
1977
1969
  });
1978
1970
  //# sourceMappingURL=index.js.map