@wtfalch/foundry 0.50.0 → 0.51.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.
@@ -0,0 +1,20 @@
1
+ -- @wtfalch/foundry: `signin_in_app` says "absent" again. In-app sign-in is on by default, so NULL
2
+ -- (the field is not declared) means on, false is a declared off, true a declared on. Every stored
3
+ -- false so far was the old column default, never a choice: it becomes NULL. Runs once: a later run
4
+ -- finds the column already nullable and leaves the declared values alone.
5
+ -- Mirrors src/store/schema.ts's `declaredOrgs`.
6
+
7
+ do $$
8
+ begin
9
+ if exists (
10
+ select 1 from information_schema.columns
11
+ where table_schema = current_schema()
12
+ and table_name = 'foundry_declared_orgs'
13
+ and column_name = 'signin_in_app'
14
+ and is_nullable = 'NO'
15
+ ) then
16
+ alter table foundry_declared_orgs alter column signin_in_app drop not null;
17
+ alter table foundry_declared_orgs alter column signin_in_app drop default;
18
+ update foundry_declared_orgs set signin_in_app = null where signin_in_app = false;
19
+ end if;
20
+ end $$;
package/dist/org.d.ts CHANGED
@@ -725,7 +725,7 @@ export interface ServicesConfig {
725
725
  }
726
726
  export declare const SFU_SLOT_MAX = 20;
727
727
  /**
728
- * Per-company sign-in placement. `inApp` (default off) moves the people's sign-in from the
728
+ * Per-company sign-in placement. `inApp` (default on: absent means on, `false` turns it off) moves the people's sign-in from the
729
729
  * company's login host onto each app's own address (`/sign-in` on agora.<domain>, boule.<domain>),
730
730
  * through the login that serves that path. Read through `plan/sign-in.ts`'s `inAppSignIn`.
731
731
  */
@@ -1053,7 +1053,7 @@ export interface DeclaredOrg {
1053
1053
  hosting: HostingConfig;
1054
1054
  /** Absent means `{ own: false }`. Optional so every existing literal and test fixture compiles unchanged, as `calls.versionPin` and `MailConfig.mailApps` do. */
1055
1055
  services?: ServicesConfig;
1056
- /** Absent means `{ inApp: false }`. Optional so every existing literal and fixture compiles unchanged. */
1056
+ /** Absent means `{ inApp: true }`. Optional so every existing literal and fixture compiles unchanged. */
1057
1057
  signIn?: SignInConfig;
1058
1058
  /** The company's Boule. Absent means no blue/green. */
1059
1059
  boule?: OrgBuiltinApp;
@@ -4016,8 +4016,8 @@ async function switchOnArchonIdTokenUserinfo(context, deps) {
4016
4016
  try {
4017
4017
  done = await zitadel.updateOidcAppIdTokenUserinfoAssertion(orgId, projectId, appId);
4018
4018
  }
4019
- catch {
4020
- throw new Error(`platform:archon: switching on user info in the ID token failed for the sign-in client ${appId}`);
4019
+ catch (error) {
4020
+ throw new Error(`platform:archon: switching on user info in the ID token failed for the sign-in client ${appId}${zitadelRefusal(error)}`);
4021
4021
  }
4022
4022
  // A client that answers every property (`packages/local`) resolves to something else: no marker.
4023
4023
  if (typeof done !== 'boolean')
@@ -4052,13 +4052,29 @@ async function updateArchonPostLogoutUris(context, deps) {
4052
4052
  try {
4053
4053
  await zitadel.updateOidcAppPostLogoutRedirectUris(orgId, projectId, appId, uris);
4054
4054
  }
4055
- catch {
4056
- throw new Error(`platform:archon: updating the post-logout addresses failed for the sign-in client ${appId}`);
4055
+ catch (error) {
4056
+ throw new Error(`platform:archon: updating the post-logout addresses failed for the sign-in client ${appId}${zitadelRefusal(error)}`);
4057
4057
  }
4058
4058
  context.recordScopeId(PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, postLogoutAppliedValue(appId, uris));
4059
4059
  await context.persist?.();
4060
4060
  return {};
4061
4061
  }
4062
+ /**
4063
+ * What ZITADEL answered, for an error about one of Archon's sign-in client repairs: its HTTP status,
4064
+ * gRPC code and own message (ZITADEL's error ids, never a token or a request body), capped. Empty for
4065
+ * anything that is not a ZITADEL answer, so a network failure names no cause it cannot vouch for.
4066
+ */
4067
+ function zitadelRefusal(error) {
4068
+ if (!(error instanceof Error) || error.name !== 'ZitadelError')
4069
+ return '';
4070
+ const { status, code } = error;
4071
+ const parts = [
4072
+ typeof status === 'number' ? `HTTP ${status}` : '',
4073
+ typeof code === 'number' ? `code ${code}` : '',
4074
+ ];
4075
+ const said = error.message.slice(0, 200);
4076
+ return ` (ZITADEL answered ${[...parts.filter(Boolean), said].join(', ')})`;
4077
+ }
4062
4078
  /**
4063
4079
  * #339: registers `/auth/desktop/callback` on Archon's sign-in client
4064
4080
  * (`zitadel:desktop-callback` keyed {@link PLATFORM_ARCHON_KEY}), keeping the
@@ -4089,8 +4105,8 @@ async function registerArchonDesktopCallback(context, deps) {
4089
4105
  await zitadel.updateOidcAppRedirectUris(orgId, projectId, appId, union);
4090
4106
  }
4091
4107
  }
4092
- catch {
4093
- throw new Error(`platform:archon: registering the desktop callback failed for the sign-in client ${appId}`);
4108
+ catch (error) {
4109
+ throw new Error(`platform:archon: registering the desktop callback failed for the sign-in client ${appId}${zitadelRefusal(error)}`);
4094
4110
  }
4095
4111
  context.recordScopeId(PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, appId);
4096
4112
  await context.persist?.();
@@ -68,7 +68,7 @@ export declare const SIGN_IN_RATE_LIMIT_SCOPE_KEY = "ratelimit_in_app";
68
68
  export declare const SIGN_IN_RATE_LIMIT_DESCRIPTION = "foundry: rate limit sign-in paths (in-app)";
69
69
  export declare const SIGN_IN_RATE_LIMIT_EXPRESSION = "(starts_with(http.request.uri.path, \"/auth/\")) or (starts_with(http.request.uri.path, \"/sign-in/\") and not starts_with(http.request.uri.path, \"/sign-in/_next/\"))";
70
70
  /**
71
- * True when the company has the switch on and it applies: a company domain whose login host is
71
+ * True when the company has the switch on (absent counts as on; only `inApp: false` turns it off) and it applies: a company domain whose login host is
72
72
  * `auth.<domain>` (`issuer.usesDomain`) and the shared sign-in. A tier 1 company on `oikos.host` keeps
73
73
  * today's flow even when the switch is set (the declaration is refused: `org.ts`'s `validateSignIn`).
74
74
  */
@@ -72,12 +72,12 @@ export const SIGN_IN_RATE_LIMIT_SCOPE_KEY = 'ratelimit_in_app';
72
72
  export const SIGN_IN_RATE_LIMIT_DESCRIPTION = 'foundry: rate limit sign-in paths (in-app)';
73
73
  export const SIGN_IN_RATE_LIMIT_EXPRESSION = '(starts_with(http.request.uri.path, "/auth/")) or (starts_with(http.request.uri.path, "/sign-in/") and not starts_with(http.request.uri.path, "/sign-in/_next/"))';
74
74
  /**
75
- * True when the company has the switch on and it applies: a company domain whose login host is
75
+ * True when the company has the switch on (absent counts as on; only `inApp: false` turns it off) and it applies: a company domain whose login host is
76
76
  * `auth.<domain>` (`issuer.usesDomain`) and the shared sign-in. A tier 1 company on `oikos.host` keeps
77
77
  * today's flow even when the switch is set (the declaration is refused: `org.ts`'s `validateSignIn`).
78
78
  */
79
79
  export function inAppSignIn(org) {
80
- return org.signIn?.inApp === true && Boolean(org.domain) && org.issuer.usesDomain;
80
+ return org.signIn?.inApp !== false && Boolean(org.domain) && org.issuer.usesDomain;
81
81
  }
82
82
  /**
83
83
  * True when an app's live env holds `SIGNIN_UPSTREAM` and the desired env does not: it is to be removed.
@@ -154,8 +154,8 @@ function rowToOrg(row) {
154
154
  },
155
155
  }
156
156
  : {}),
157
- // Omitted while off.
158
- ...(row.signInInApp ? { signIn: { inApp: true } } : {}),
157
+ // NULL is an absent field (on by default); false and true are declared.
158
+ ...(row.signInInApp === null ? {} : { signIn: { inApp: row.signInInApp } }),
159
159
  // Omitted while off.
160
160
  ...(row.customersEnabled || row.customersSignUp
161
161
  ? { customers: { enabled: row.customersEnabled, signUp: row.customersSignUp } }
@@ -226,7 +226,7 @@ function orgToRow(org) {
226
226
  callsVersionPinSynapse: org.calls.versionPin?.synapse ?? null,
227
227
  callsVersionPinMas: org.calls.versionPin?.mas ?? null,
228
228
  issuerUsesDomain: org.issuer.usesDomain,
229
- signInInApp: org.signIn?.inApp ?? false,
229
+ signInInApp: org.signIn?.inApp ?? null,
230
230
  customersEnabled: org.customers?.enabled ?? false,
231
231
  customersSignUp: org.customers?.signUp ?? false,
232
232
  bouleBlueGreen: org.boule?.blueGreen ?? null,
@@ -362,8 +362,8 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
362
362
  columnType: "PgBoolean";
363
363
  data: boolean;
364
364
  driverParam: boolean;
365
- notNull: true;
366
- hasDefault: true;
365
+ notNull: false;
366
+ hasDefault: false;
367
367
  isPrimaryKey: false;
368
368
  isAutoincrement: false;
369
369
  hasRuntimeDefault: false;
@@ -38,8 +38,8 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
38
38
  // ServicesConfig (org.ts): false and null are the platform's shared services.
39
39
  servicesOwn: boolean('services_own').notNull().default(false),
40
40
  servicesSfuSlot: integer('services_sfu_slot'),
41
- // SignInConfig.inApp (org.ts); migrations/0054.
42
- signInInApp: boolean('signin_in_app').notNull().default(false),
41
+ // SignInConfig.inApp (org.ts); migrations/0054, nullable in 0061: NULL is an absent field (on).
42
+ signInInApp: boolean('signin_in_app'),
43
43
  // CustomersConfig (org.ts); migrations/0057.
44
44
  customersEnabled: boolean('customers_enabled').notNull().default(false),
45
45
  customersSignUp: boolean('customers_sign_up').notNull().default(false),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
5
5
  "repository": {
6
6
  "type": "git",