@wtfalch/foundry 0.41.2 → 0.42.1
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.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations/0054_org_signin_in_app.sql +6 -0
- package/dist/org.d.ts +17 -0
- package/dist/org.js +14 -0
- package/dist/plan/agora.d.ts +4 -1
- package/dist/plan/agora.js +16 -2
- package/dist/plan/boule.d.ts +5 -2
- package/dist/plan/boule.js +14 -2
- package/dist/plan/coolify-previews.d.ts +10 -0
- package/dist/plan/coolify-previews.js +14 -0
- package/dist/plan/diff.js +92 -9
- package/dist/plan/edge-ratelimits.d.ts +5 -0
- package/dist/plan/edge-ratelimits.js +16 -6
- package/dist/plan/executor.d.ts +45 -0
- package/dist/plan/executor.js +283 -22
- package/dist/plan/login-host.d.ts +23 -2
- package/dist/plan/login-host.js +30 -5
- package/dist/plan/own-diff.js +6 -0
- package/dist/plan/platform-diff.js +4 -0
- package/dist/plan/platform-org.d.ts +6 -0
- package/dist/plan/platform-org.js +5 -0
- package/dist/plan/platform-providers.d.ts +16 -1
- package/dist/plan/platform-providers.js +255 -8
- package/dist/plan/providers-readers.js +6 -2
- package/dist/plan/sign-in-archon.d.ts +46 -0
- package/dist/plan/sign-in-archon.js +71 -0
- package/dist/plan/sign-in-bindings.d.ts +16 -0
- package/dist/plan/sign-in-bindings.js +44 -0
- package/dist/plan/sign-in.d.ts +135 -0
- package/dist/plan/sign-in.js +172 -0
- package/dist/store/postgres.js +7 -1
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/schema.js +2 -0
- package/package.json +1 -1
package/dist/plan/login-host.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ownsServices } from '../org.js';
|
|
2
|
-
import { AGORA_CLIENT_ID_SCOPE_KEY, APP_CLIENT_ID_SCOPE_KEY, CMS_CLIENT_ID_SCOPE_KEY, ENTRY_CLIENT_ID_SCOPE_PREFIX, } from './agora.js';
|
|
3
|
-
import { BOULE_CLIENT_ID_SCOPE_KEY } from './boule.js';
|
|
2
|
+
import { AGORA_CLIENT_ID_SCOPE_KEY, APP_CLIENT_ID_SCOPE_KEY, CMS_CLIENT_ID_SCOPE_KEY, ENTRY_CLIENT_ID_SCOPE_PREFIX, agoraHostnameFor, } from './agora.js';
|
|
3
|
+
import { BOULE_CLIENT_ID_SCOPE_KEY, bouleHostnameFor } from './boule.js';
|
|
4
|
+
import { SIGN_IN_IN_APP_SCOPE_KEY, inAppSignIn, inAppStatePairs, } from './sign-in.js';
|
|
4
5
|
/**
|
|
5
6
|
* #267: what both the planner (`diff.ts`) and the executor (`executor.ts`)
|
|
6
7
|
* need to agree on for an org's own company login host -- the hostname and
|
|
@@ -120,7 +121,10 @@ export function loginHostServesAgora(host, org, recorded) {
|
|
|
120
121
|
if (!bound?.startsWith(`${host} `))
|
|
121
122
|
return false;
|
|
122
123
|
const clientId = recorded[AGORA_CLIENT_ID_SCOPE_KEY];
|
|
124
|
+
// Agora's client moves to its own host's binding once that host signs in on the app's address.
|
|
125
|
+
const movedToAppHost = inAppStatePairs(recorded[SIGN_IN_IN_APP_SCOPE_KEY]).some((pair) => pair.clientId === clientId);
|
|
123
126
|
return (!clientId ||
|
|
127
|
+
movedToAppHost ||
|
|
124
128
|
bound
|
|
125
129
|
.slice(host.length + 1)
|
|
126
130
|
.split(',')
|
|
@@ -132,6 +136,23 @@ export function loginHostServesAgora(host, org, recorded) {
|
|
|
132
136
|
* instance, so `destroy` must not remove it.
|
|
133
137
|
*/
|
|
134
138
|
export const LOGIN_HOST_SHARED_SCOPE_KEY = 'zitadel_login_host_shared';
|
|
139
|
+
/**
|
|
140
|
+
* The app hosts that sign in on their own address when the company has `signIn.inApp`: Agora's and
|
|
141
|
+
* Boule's, each only once its client id is recorded. Empty with the switch off. Archon (owner company
|
|
142
|
+
* only) is not here: the platform's Archon step owns its host (`sign-in-archon.ts`).
|
|
143
|
+
*/
|
|
144
|
+
export function inAppApps(org, recorded) {
|
|
145
|
+
if (!inAppSignIn(org))
|
|
146
|
+
return [];
|
|
147
|
+
const apps = [];
|
|
148
|
+
const agora = recorded[AGORA_CLIENT_ID_SCOPE_KEY];
|
|
149
|
+
if (agora)
|
|
150
|
+
apps.push({ app: 'agora', host: agoraHostnameFor(org), clientId: agora });
|
|
151
|
+
const boule = recorded[BOULE_CLIENT_ID_SCOPE_KEY];
|
|
152
|
+
if (boule)
|
|
153
|
+
apps.push({ app: 'boule', host: bouleHostnameFor(org), clientId: boule });
|
|
154
|
+
return apps.sort((a, b) => (a.host < b.host ? -1 : 1));
|
|
155
|
+
}
|
|
135
156
|
/**
|
|
136
157
|
* Scope id holding what the binding was last written with
|
|
137
158
|
* ({@link loginBindingState}). Client ids are not secrets. The planner
|
|
@@ -149,8 +170,12 @@ export const LOGIN_BINDING_SCOPE_KEY = 'login_host_binding';
|
|
|
149
170
|
export function customersEntryKeys(org) {
|
|
150
171
|
return new Set(org.hosting.entries.filter((e) => e.uses?.auth?.users === 'customers').map((e) => e.key));
|
|
151
172
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The org's recorded app client ids (hosted app, each entry's own bar a customers entry, cms, agora, boule),
|
|
175
|
+
* deduplicated and sorted, bar `except`: the login refuses a client id held by two bindings, so the ids of the
|
|
176
|
+
* app hosts that have their own binding (in-app sign-in) are not in the company's.
|
|
177
|
+
*/
|
|
178
|
+
export function loginHostClientIds(org, recorded, except = []) {
|
|
154
179
|
const customers = customersEntryKeys(org);
|
|
155
180
|
const ids = [
|
|
156
181
|
recorded[APP_CLIENT_ID_SCOPE_KEY],
|
|
@@ -162,7 +187,7 @@ export function loginHostClientIds(org, recorded) {
|
|
|
162
187
|
!customers.has(key.slice(ENTRY_CLIENT_ID_SCOPE_PREFIX.length)))
|
|
163
188
|
.map(([, id]) => id),
|
|
164
189
|
].filter((id) => Boolean(id));
|
|
165
|
-
return [...new Set(ids)].sort();
|
|
190
|
+
return [...new Set(ids)].filter((id) => !except.includes(id)).sort();
|
|
166
191
|
}
|
|
167
192
|
/** The host plus its sorted client ids, as recorded under {@link LOGIN_BINDING_SCOPE_KEY}. */
|
|
168
193
|
export function loginBindingState(host, clientIds) {
|
package/dist/plan/own-diff.js
CHANGED
|
@@ -13,6 +13,7 @@ import { OWN_SFU_APPLIED_HOSTNAME_SCOPE_KEY, OWN_SFU_APPLIED_NODE_IP_SCOPE_KEY,
|
|
|
13
13
|
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_SCOPE_KEY, PLATFORM_AUTH_INSTANCE_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_FILES_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_SCOPE_KEY, PLATFORM_FILES_INTROSPECTION_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_ADMIN_KEY_SCOPE_KEY, PLATFORM_MAIL_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_MAIL_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_MAIL_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
14
14
|
import { OWN_FILES_RUNNER_SCOPE_KEY } from './platform-org.js';
|
|
15
15
|
import { JOBS_ADMIN_TOKEN_UNDELIVERED_SCOPE_KEY, JOBS_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_DB_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_SCOPE_KEY, PLATFORM_FILES_SHARE_DOMAINS_SCOPE_KEY, filesShareDomains, } from './platform-providers.js';
|
|
16
|
+
import { SIGN_IN_LOGIN_APP_SCOPE_KEY, inAppSignIn } from './sign-in.js';
|
|
16
17
|
import { signinDbBackupMissing } from './signin-db-backup.js';
|
|
17
18
|
import { SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY, signinDbCertDue } from './signin-db-tls.js';
|
|
18
19
|
import { SIGNIN_DB_TLS_SCOPE_KEY } from './signin-db.js';
|
|
@@ -162,6 +163,11 @@ export function planOwnAuth(options) {
|
|
|
162
163
|
recordedIds[PLATFORM_AUTH_SMTP_SCOPE_KEY] &&
|
|
163
164
|
recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS &&
|
|
164
165
|
(Boolean(recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY]) || !brokerHoldsAnEntry) &&
|
|
166
|
+
// In-app sign-in: the second login app is recorded once it is deployed and holds the token, which
|
|
167
|
+
// needs the login client; until the broker holds an entry that cannot be, so the step waits.
|
|
168
|
+
(!inAppSignIn(org) ||
|
|
169
|
+
Boolean(recordedIds[SIGN_IN_LOGIN_APP_SCOPE_KEY]) ||
|
|
170
|
+
!brokerHoldsAnEntry) &&
|
|
165
171
|
!signinDbCertDue(recordedIds, options.now ?? Date.now()) &&
|
|
166
172
|
!signinDbBackupMissing(recordedIds) &&
|
|
167
173
|
// ZITADEL was started on the TLS database and never seen healthy there: the step starts it again.
|
|
@@ -11,6 +11,7 @@ import { PLATFORM_MAIL_RELAY_KEY, planMailRelay } from './mail-relay.js';
|
|
|
11
11
|
import { MAIL_DATABASE_SCOPE_KEY, mailStoreIsLegacy, mailStoreSwitchChange, mailStoreSwitchDescription, } from './mail-store.js';
|
|
12
12
|
import { planOikosIngress } from './oikos-ingress.js';
|
|
13
13
|
import { callsSfuAppliedFingerprint } from './platform-sfu.js';
|
|
14
|
+
import { archonInAppHost } from './sign-in-archon.js';
|
|
14
15
|
import { signInDomainBlockedText } from './sign-in-org.js';
|
|
15
16
|
import { withStepSystem } from './step-system.js';
|
|
16
17
|
/**
|
|
@@ -3055,6 +3056,9 @@ function planArchon(platform, recordedIds, executorBlocked, ownerSignIn, ownerFi
|
|
|
3055
3056
|
recordedIds[PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY] === ownerSignIn.organisationId &&
|
|
3056
3057
|
(recordedIds[PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY] ?? '') ===
|
|
3057
3058
|
archonFirstOwnerLink(recordedIds, ownerSignIn) &&
|
|
3059
|
+
// In-app sign-in: Archon's own address is the sign-in address only once the owner company has the
|
|
3060
|
+
// switch on and its in-app login is applied, and goes back to the company's login host when it does not.
|
|
3061
|
+
archonInAppHost(recordedIds) === (ownerSignIn.signInInApp ? platform.archon.hostname : '') &&
|
|
3058
3062
|
recordedIds[PLATFORM_ARCHON_APP_KEY_ORG_SCOPE_KEY] === archonOrgId &&
|
|
3059
3063
|
!recordedIds[PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY] &&
|
|
3060
3064
|
Boolean(recordedIds[PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY]) &&
|
|
@@ -102,6 +102,8 @@ export interface OrgPlatformReadiness {
|
|
|
102
102
|
* platform's, not as strangers. Absent when none is recorded.
|
|
103
103
|
*/
|
|
104
104
|
platformZitadelIds?: readonly string[];
|
|
105
|
+
/** The second login app that serves `/sign-in` for companies with `signIn.inApp` is applied. Absent reads as false. */
|
|
106
|
+
signInLoginReady?: boolean;
|
|
105
107
|
/** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
|
|
106
108
|
authReady: boolean;
|
|
107
109
|
/**
|
|
@@ -310,6 +312,10 @@ export interface OwnerSignIn {
|
|
|
310
312
|
* is declared, absent while the owner step has recorded nothing.
|
|
311
313
|
*/
|
|
312
314
|
firstOwnerUserId?: string | null;
|
|
315
|
+
/** The company's in-app login app (`<slug>-auth-login-sign-in`), once it is applied. Absent before. */
|
|
316
|
+
signInLoginAppId?: string;
|
|
317
|
+
/** True when the company has `signIn.inApp` and that login app is applied: Archon signs in on its own address. */
|
|
318
|
+
signInInApp?: true;
|
|
313
319
|
}
|
|
314
320
|
/** The owner company's own files host, as Archon's `FILES_*` env reads it. */
|
|
315
321
|
export interface OwnerFiles {
|
|
@@ -7,6 +7,7 @@ import { oikosIngressApplied } from './oikos-ingress.js';
|
|
|
7
7
|
import { ownHostnames } from './own-hostnames.js';
|
|
8
8
|
import { OWN_SERVICES_APPLIED_SCOPE_KEY, orgTarget } from './own-services.js';
|
|
9
9
|
import { JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_BROKER_SCOPE_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
10
|
+
import { SIGN_IN_LOGIN_APP_SCOPE_KEY, inAppSignIn } from './sign-in.js';
|
|
10
11
|
/**
|
|
11
12
|
* #443: the no-domain company mail apex -- `<slug>.oikos.host`, the same
|
|
12
13
|
* fixed suffix `agora.ts`'s `agoraHostnameFor`, `boule.ts`'s
|
|
@@ -159,6 +160,7 @@ edgePortPresent = false) {
|
|
|
159
160
|
...(coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY)
|
|
160
161
|
? { platformAuthRecordedHost: platform.auth.hostname }
|
|
161
162
|
: {}),
|
|
163
|
+
...(platformRecordedIds[SIGN_IN_LOGIN_APP_SCOPE_KEY] ? { signInLoginReady: true } : {}),
|
|
162
164
|
authReady,
|
|
163
165
|
authIssuer: authReady ? `https://${platform.auth.hostname}` : null,
|
|
164
166
|
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
@@ -314,6 +316,7 @@ export function ownerSignInFrom(owner, recorded) {
|
|
|
314
316
|
if (!applied || applied !== ownHostnames(owner).auth)
|
|
315
317
|
return null;
|
|
316
318
|
const hostname = applied;
|
|
319
|
+
const signInLoginAppId = recorded[SIGN_IN_LOGIN_APP_SCOPE_KEY];
|
|
317
320
|
return {
|
|
318
321
|
issuerUrl: `https://${applied}`,
|
|
319
322
|
organisationId,
|
|
@@ -321,6 +324,8 @@ export function ownerSignInFrom(owner, recorded) {
|
|
|
321
324
|
bearerRef: orgTarget(owner).refs.instanceBearer,
|
|
322
325
|
hostname,
|
|
323
326
|
firstOwnerUserId: platformOwnerUserIdFrom(owner, recorded),
|
|
327
|
+
...(signInLoginAppId ? { signInLoginAppId } : {}),
|
|
328
|
+
...(signInLoginAppId && inAppSignIn(owner) ? { signInInApp: true } : {}),
|
|
324
329
|
};
|
|
325
330
|
}
|
|
326
331
|
/**
|
|
@@ -408,6 +408,14 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
408
408
|
* (or the runner was never applied).
|
|
409
409
|
*/
|
|
410
410
|
refreshFilesShare(_platform: DeclaredPlatform, recordedIds: Readonly<Record<string, string>>): Promise<Record<string, string> | null>;
|
|
411
|
+
/** True while a company signs in on its apps' own addresses through the platform's login. */
|
|
412
|
+
private signInLoginWanted;
|
|
413
|
+
/**
|
|
414
|
+
* In-app sign-in: the login app that serves `/sign-in` is wanted and either not recorded, gone from
|
|
415
|
+
* Coolify, or holding a host table other than the login's. The step then plans again and repairs it.
|
|
416
|
+
* A Coolify read that fails counts as not drifted, with one fixed log line (the error can carry values).
|
|
417
|
+
*/
|
|
418
|
+
private signInLoginDrifted;
|
|
411
419
|
execute(action: StepAction, context: PlatformStepExecutionContext): Promise<{
|
|
412
420
|
resourceId?: string;
|
|
413
421
|
} | undefined>;
|
|
@@ -510,6 +518,13 @@ export interface AuthStackInput {
|
|
|
510
518
|
defaultRedirectUri: string | null;
|
|
511
519
|
/** The company whose owner is invited into its own sign-in org; null for a company's own stack. */
|
|
512
520
|
ownerCompany: DeclaredOrg | null;
|
|
521
|
+
/**
|
|
522
|
+
* In-app sign-in: a second login app serves `/sign-in` next to the login app. Absent: none is made.
|
|
523
|
+
* `bindings` is its `LOGIN_HOST_BINDINGS` document; `null` copies the login app's live one.
|
|
524
|
+
*/
|
|
525
|
+
signInLogin?: {
|
|
526
|
+
bindings: string | null;
|
|
527
|
+
};
|
|
513
528
|
}
|
|
514
529
|
/**
|
|
515
530
|
* #168: makes `coolify:app` keyed {@link PLATFORM_AUTH_KEY} true -- a Coolify
|
|
@@ -890,7 +905,7 @@ export interface PlatformArchonDeps {
|
|
|
890
905
|
access: PlatformAccessFactory;
|
|
891
906
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
892
907
|
/** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
|
|
893
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp' | 'updateOidcAppIdTokenUserinfoAssertion'
|
|
908
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp' | 'updateOidcAppIdTokenUserinfoAssertion'> & Partial<Pick<ZitadelPort, 'ensureTrustedDomain' | 'deleteTrustedDomain' | 'updateOidcAppLoginBaseUri' | 'allowPerAppLoginBaseUri'>>;
|
|
894
909
|
/**
|
|
895
910
|
* {@link PlatformAuthDeps.zitadelForInstance}, for Archon's sign-in client:
|
|
896
911
|
* the owner company's own ZITADEL, by its issuer and the held-key reference of
|