@wtfalch/foundry 0.45.0 → 0.46.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.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/org.d.ts +37 -0
- package/dist/org.js +135 -10
- package/dist/plan/agora.d.ts +14 -0
- package/dist/plan/agora.js +21 -0
- package/dist/plan/backup-gate.d.ts +15 -0
- package/dist/plan/backup-gate.js +38 -0
- package/dist/plan/boule-owners-http.d.ts +12 -0
- package/dist/plan/boule-owners-http.js +59 -0
- package/dist/plan/boule.d.ts +38 -2
- package/dist/plan/boule.js +72 -1
- package/dist/plan/company-backups.d.ts +7 -1
- package/dist/plan/company-backups.js +42 -2
- package/dist/plan/cookie-domain.d.ts +41 -0
- package/dist/plan/cookie-domain.js +71 -0
- package/dist/plan/desktop-auth.d.ts +58 -0
- package/dist/plan/desktop-auth.js +144 -0
- package/dist/plan/desktop-callback.d.ts +26 -0
- package/dist/plan/desktop-callback.js +59 -0
- package/dist/plan/diff.d.ts +13 -4
- package/dist/plan/diff.js +272 -84
- package/dist/plan/executor.d.ts +136 -6
- package/dist/plan/executor.js +725 -98
- package/dist/plan/health-gaps.js +5 -0
- package/dist/plan/index.d.ts +3 -0
- package/dist/plan/index.js +2 -0
- package/dist/plan/job-app.d.ts +23 -0
- package/dist/plan/job-app.js +41 -0
- package/dist/plan/live.d.ts +6 -0
- package/dist/plan/live.js +16 -0
- package/dist/plan/login-host.d.ts +28 -0
- package/dist/plan/login-host.js +36 -0
- package/dist/plan/org-names.js +9 -1
- package/dist/plan/org-postgres-job.d.ts +22 -0
- package/dist/plan/org-postgres-job.js +91 -0
- package/dist/plan/org-postgres-migrate.d.ts +115 -0
- package/dist/plan/org-postgres-migrate.js +466 -0
- package/dist/plan/org-postgres.d.ts +90 -0
- package/dist/plan/org-postgres.js +293 -0
- package/dist/plan/plan-apply.d.ts +30 -2
- package/dist/plan/plan-apply.js +104 -7
- package/dist/plan/platform-diff.d.ts +45 -0
- package/dist/plan/platform-diff.js +159 -4
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-org.js +4 -0
- package/dist/plan/platform-providers.d.ts +12 -2
- package/dist/plan/platform-providers.js +279 -9
- package/dist/plan/post-logout-uris.d.ts +22 -0
- package/dist/plan/post-logout-uris.js +43 -0
- package/dist/plan/providers-readers.d.ts +10 -1
- package/dist/plan/providers-readers.js +41 -1
- package/dist/plan/sign-in-bindings.d.ts +4 -0
- package/dist/plan/sign-in-bindings.js +10 -0
- package/dist/plan/sign-in-org.d.ts +19 -6
- package/dist/plan/sign-in-org.js +35 -10
- package/dist/plan/sign-in.d.ts +12 -5
- package/dist/plan/sign-in.js +12 -5
- package/dist/plan/signin-db-move.js +10 -34
- package/package.json +5 -5
|
@@ -11,21 +11,24 @@ import { recordBuiltCommit } from './build-record.js';
|
|
|
11
11
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
12
12
|
import { previewsOn } from './coolify-previews.js';
|
|
13
13
|
import { waitForDeploymentFinished, waitForHealthyVersion, waitForNewDeployment, } from './deploy-health.js';
|
|
14
|
+
import { desktopCallbackUri, unionRedirectUris } from './desktop-callback.js';
|
|
14
15
|
import { placedOnPlatform } from './destinations.js';
|
|
15
16
|
import { ACCESS_POLICY_NAME, LOG_DIR, coolifyProjectMark, getDatabaseStarted, isNotFoundError, isRunningOrStarting, } from './executor.js';
|
|
16
17
|
import { ACCESS_PROVIDER_RECORD_KEY, GATE_CLAIM_NAME } from './gates.js';
|
|
17
18
|
import { IMAGE_HEALTHCHECK_SETTINGS, commandHealthCheckSettings, healthCheckSettings, tcpProbe, } from './health-check-settings.js';
|
|
18
19
|
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './held-key-refs.js';
|
|
19
20
|
import { LIMITS, limited } from './own-limits.js';
|
|
21
|
+
import { postLogoutAppliedValue, postLogoutUrisFor } from './post-logout-uris.js';
|
|
20
22
|
import { createReleaseHost } from './release-host.js';
|
|
21
23
|
export { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './held-key-refs.js';
|
|
24
|
+
import { SIGN_IN_BIND_KEY_ENV, SIGN_IN_BIND_KEY_MIN_LENGTH, SIGN_IN_BIND_KEY_SCOPE_KEY, } from './cookie-domain.js';
|
|
22
25
|
import { coolifyDomains, coolifyTunnelDomains, filesShareHostname } from './hostnames.js';
|
|
23
26
|
import { MAIL_RELAY_BLOCKED, provisionMailRelay } from './mail-relay.js';
|
|
24
27
|
import { MAIL_APP_SCOPE_KEY, MAIL_DATABASE_NAME, MAIL_DATABASE_PASSWORD_ENV, MAIL_DATABASE_SCOPE_KEY, MAIL_STORE_POSTGRES, MAIL_STORE_SCOPE_KEY, mailStoreIsLegacy, } from './mail-store.js';
|
|
25
28
|
import { OIKOS_INGRESS_BLOCKED, PLATFORM_OIKOS_INGRESS_KEY, oikosIngressApplied, oikosIngressZoneBlocked, provisionOikosIngress, } from './oikos-ingress.js';
|
|
26
29
|
import { recordOwnDnsHostname } from './own-dns.js';
|
|
27
30
|
import { platformNetwork, platformTarget, scopeFromPlatform, } from './own-services.js';
|
|
28
|
-
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_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_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_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_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_HC_ACCESS_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_HEARTBEATS_HOSTED_API_KEY_REF, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_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_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeVarsDigest, } from './platform-diff.js';
|
|
31
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_DEPLOY_PENDING_SCOPE_KEY, JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_APP_SCOPE_KEY, PLATFORM_ACCESS_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_AI_CONNECTOR_APP_SCOPE_KEY, PLATFORM_AI_DEPLOY_PENDING_SCOPE_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AI_MANAGEMENT_KEY_CONFLICT_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_AI_MANAGEMENT_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_MIGRATE_BLOCKED_SCOPE_KEY, PLATFORM_AI_ORGANISATION_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_AI_TUNNEL_SCOPE_KEY, PLATFORM_ARCHON_ACCESS_PIN_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_AUTH_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_CI_ORG_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FILES_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_FIRST_OWNER_LINK_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY, PLATFORM_ARCHON_APP_KEY_ORG_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_COOLIFY_READ_TOKEN_REF, PLATFORM_ARCHON_DEPLOYMENT_SCOPE_KEY, PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_ENVS_DEPLOYED_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, PLATFORM_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY, PLATFORM_ARCHON_ORG_POLICY_SCOPE_KEY, PLATFORM_ARCHON_ORG_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY, PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, PLATFORM_ARCHON_RETIRED_CLIENT_SCOPE_KEY, PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY, PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_AUTH_BROKER_DEPLOYED_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_KEY, PLATFORM_AUTH_LOGIN_CLIENT_UNDELIVERED_SCOPE_KEY, PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY, PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, PLATFORM_AUTH_REGISTRATION_CLOSED_SCOPE_KEY, PLATFORM_AUTH_RESERVED_ADMIN_USERNAME_SCOPE_KEY, PLATFORM_AUTH_SMTP_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, PLATFORM_EDGE_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_EDGE_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF, PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY, PLATFORM_EDGE_KEY, PLATFORM_EDGE_KEY_REF, PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY, PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_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_KEY, PLATFORM_FILES_MANAGEMENT_KEY_SCOPE_KEY, PLATFORM_FILES_MANAGEMENT_UNDELIVERED_SCOPE_KEY, PLATFORM_FILES_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_HC_ACCESS_SCOPE_KEY, PLATFORM_HC_API_KEY_REF, PLATFORM_HC_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_HC_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_HC_KEY, PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY, PLATFORM_HEARTBEATS_HOSTED_API_KEY_REF, PLATFORM_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_ID_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_PREVIOUS_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_REQUESTED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_ROTATION_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_INTEGRATIONS_RUNNER_KEYRING_DEPLOYED_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_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_HOST_PORTS_DIRECT, PLATFORM_MAIL_HOST_PORTS_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_RELAY_KEY, PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY, PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY, PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY, PLATFORM_MAIL_ROUTE_SCOPE_KEY, PLATFORM_MAIL_ROUTE_WANTED, PLATFORM_MAIL_USER_ROLE, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_BLOCKED_SCOPE_KEY, PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY, PLATFORM_OWNER_ORG_SCOPE_KEY, PLATFORM_SFU_APPLIED_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF, PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF, PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_ACCESS_TOKEN_NAME, PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY, PLATFORM_UPTIME_CHAT_ROOM_ID_SCOPE_KEY, PLATFORM_UPTIME_HEARTBEAT_CHECK_NAME, PLATFORM_UPTIME_KEY, PLATFORM_UPTIME_REMOVED_SCOPE_KEY, PLATFORM_UPTIME_SCRIPT_NAME, PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY, PLATFORM_UPTIME_TOKEN_REF, PLATFORM_ZITADEL_ORG_SCOPE_KEY, archonFilesMarker, archonFirstOwnerLink, archonOrgDomain, archonOrgName, desiredHeartbeatCheckNames, heartbeatCheckUrlRef, parseBrokerDeployRecord, parseLoginClientRecord, platformBackupHeartbeatCheckName, platformMailHostPorts, platformMailMxHost, uptimeAccessTokenConflict, uptimeEffectiveVars, uptimeRecorded, uptimeVarsDigest, } from './platform-diff.js';
|
|
29
32
|
import { PLATFORM_ARCHON_IN_APP_SCOPE_KEY, archonInAppBack, archonInAppBind, archonInAppHost, archonInAppPoint, archonInAppUnbind, } from './sign-in-archon.js';
|
|
30
33
|
import { LOGIN_HOST_BINDINGS_ENV } from './sign-in-bindings.js';
|
|
31
34
|
import { ensureSignInOrg, inviteOwnerWithLink, signInOrgDomain } from './sign-in-org.js';
|
|
@@ -497,12 +500,32 @@ export class FoundryPlatformStepExecutor {
|
|
|
497
500
|
: undefined;
|
|
498
501
|
if (zitadel?.updateOidcAppIdTokenUserinfoAssertion)
|
|
499
502
|
blocked.archonIdTokenUserinfo = true;
|
|
503
|
+
if (zitadel?.updateOidcAppPostLogoutRedirectUris)
|
|
504
|
+
blocked.archonPostLogoutUris = true;
|
|
505
|
+
if (zitadel?.getOidcAppRedirectUris && zitadel.updateOidcAppRedirectUris) {
|
|
506
|
+
blocked.archonDesktopCallback = true;
|
|
507
|
+
}
|
|
500
508
|
}
|
|
501
509
|
catch (e) {
|
|
502
510
|
// No ZITADEL client for this instance: the flag stays unset and the repair is not planned.
|
|
503
511
|
console.error(`platform:archon: could not reach the platform's ZITADEL client to plan the ID token repair: ${e instanceof Error ? e.message : String(e)}`);
|
|
504
512
|
}
|
|
505
513
|
}
|
|
514
|
+
// Not a block: without a client that can make a project role and grant it, the mail role step is not planned.
|
|
515
|
+
if (this.mail && platform.mail.enabled) {
|
|
516
|
+
try {
|
|
517
|
+
const mailZitadel = this.mail.zitadelForInstance?.(`https://${platform.auth.hostname}`) ?? this.mail.zitadel;
|
|
518
|
+
if (mailZitadel.ensureProjectRole && mailZitadel.grantProjectRoles) {
|
|
519
|
+
blocked.mailRole = true;
|
|
520
|
+
if (this.mail.requireMailRole && mailZitadel.setProjectRoleChecks) {
|
|
521
|
+
blocked.mailRoleCheck = true;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (e) {
|
|
526
|
+
console.error(`platform:mail: could not reach the platform's ZITADEL client to plan the mail role: ${e instanceof Error ? e.message : String(e)}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
506
529
|
// Not a block: without the auth dependencies and a ZITADEL client for the instance the sign-in policy step is not planned.
|
|
507
530
|
if (this.auth && platform.auth.enabled && recordedIds[PLATFORM_ZITADEL_ORG_SCOPE_KEY]) {
|
|
508
531
|
try {
|
|
@@ -684,6 +707,8 @@ export class FoundryPlatformStepExecutor {
|
|
|
684
707
|
if (loginClientDrifted || (await this.signInLoginDrifted(this.auth, recordedIds))) {
|
|
685
708
|
result.auth = true;
|
|
686
709
|
}
|
|
710
|
+
if (await authAppsNotRunning(this.auth.coolify, recordedIds))
|
|
711
|
+
result.authApps = true;
|
|
687
712
|
}
|
|
688
713
|
// #380: once the runner app is recorded, re-check its live env against
|
|
689
714
|
// every setting `ensureStarted()` requires -- the gap that left the
|
|
@@ -757,6 +782,9 @@ export class FoundryPlatformStepExecutor {
|
|
|
757
782
|
const loginId = recordedIds[PLATFORM_AUTH_LOGIN_SCOPE_KEY];
|
|
758
783
|
if (!signInId)
|
|
759
784
|
return true;
|
|
785
|
+
// The session bind key is not yet live on the in-app login: plan again and mint it.
|
|
786
|
+
if (recordedIds[SIGN_IN_BIND_KEY_SCOPE_KEY] !== '1')
|
|
787
|
+
return true;
|
|
760
788
|
if (!loginId)
|
|
761
789
|
return false;
|
|
762
790
|
try {
|
|
@@ -779,7 +807,7 @@ export class FoundryPlatformStepExecutor {
|
|
|
779
807
|
throw new Error('platform:auth needs FoundryPlatformStepExecutor to be constructed with its auth dependencies (#168)');
|
|
780
808
|
}
|
|
781
809
|
const company = await this.declaredOrgStore.get(context.platform.ownerOrgId);
|
|
782
|
-
return provisionPlatformAuth(context, this.limitedOnPlatform(this.auth, LIMITS.platformAuth), company, await this.signInLoginWanted());
|
|
810
|
+
return provisionPlatformAuth(context, this.limitedOnPlatform(this.auth, LIMITS.platformAuth), { ownerCompany: company, signInLogin: await this.signInLoginWanted() });
|
|
783
811
|
}
|
|
784
812
|
if (action.system === 'coolify' && action.kind === 'app' && action.key === PLATFORM_FILES_KEY) {
|
|
785
813
|
if (!this.files) {
|
|
@@ -857,6 +885,30 @@ export class FoundryPlatformStepExecutor {
|
|
|
857
885
|
}
|
|
858
886
|
return switchOnArchonIdTokenUserinfo(context, this.archon);
|
|
859
887
|
}
|
|
888
|
+
if (action.system === 'zitadel' &&
|
|
889
|
+
action.kind === 'post-logout-uris' &&
|
|
890
|
+
action.key === PLATFORM_ARCHON_KEY) {
|
|
891
|
+
if (!this.archon) {
|
|
892
|
+
throw new Error('platform:archon needs FoundryPlatformStepExecutor to be constructed with its archon dependencies (#175)');
|
|
893
|
+
}
|
|
894
|
+
return updateArchonPostLogoutUris(context, this.archon);
|
|
895
|
+
}
|
|
896
|
+
if (action.system === 'zitadel' &&
|
|
897
|
+
action.kind === 'desktop-callback' &&
|
|
898
|
+
action.key === PLATFORM_ARCHON_KEY) {
|
|
899
|
+
if (!this.archon) {
|
|
900
|
+
throw new Error('platform:archon needs FoundryPlatformStepExecutor to be constructed with its archon dependencies (#175)');
|
|
901
|
+
}
|
|
902
|
+
return registerArchonDesktopCallback(context, this.archon);
|
|
903
|
+
}
|
|
904
|
+
if (action.system === 'zitadel' &&
|
|
905
|
+
action.kind === 'mail-role' &&
|
|
906
|
+
action.key === PLATFORM_MAIL_KEY) {
|
|
907
|
+
if (!this.mail) {
|
|
908
|
+
throw new Error('platform:mail needs FoundryPlatformStepExecutor to be constructed with its mail dependencies (#169)');
|
|
909
|
+
}
|
|
910
|
+
return applyPlatformMailRole(context, this.mail);
|
|
911
|
+
}
|
|
860
912
|
if (action.system === 'zitadel' &&
|
|
861
913
|
action.kind === 'sign-in-policy' &&
|
|
862
914
|
action.key === PLATFORM_AUTH_KEY) {
|
|
@@ -1691,10 +1743,16 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1691
1743
|
});
|
|
1692
1744
|
const loginAppId = loginApp.appId;
|
|
1693
1745
|
await record(PLATFORM_AUTH_LOGIN_SCOPE_KEY, loginAppId);
|
|
1746
|
+
// A declared `gitRef` that differs from the one the apps were last built from reaches all three apps
|
|
1747
|
+
// that build from the auth repo: `git_branch` is patched and each is redeployed below.
|
|
1748
|
+
const authRefChanged = target.kind === 'platform' && recorded[PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY] !== gitRef;
|
|
1749
|
+
// An app made in this run was created on `gitRef` already.
|
|
1750
|
+
const gitBranchPatchFor = (app) => authRefChanged && !app.justCreated ? { git_branch: gitRef } : {};
|
|
1694
1751
|
// The Dockerfile's own HEALTHCHECK is the live check (`IMAGE_HEALTHCHECK_SETTINGS`).
|
|
1695
1752
|
await deps.coolify.updateApplication(loginAppId, {
|
|
1696
1753
|
custom_internal_name: target.internal.authLogin,
|
|
1697
1754
|
...IMAGE_HEALTHCHECK_SETTINGS,
|
|
1755
|
+
...gitBranchPatchFor(loginApp),
|
|
1698
1756
|
});
|
|
1699
1757
|
// ---- the in-app login app (`/sign-in`) ------------------------------------
|
|
1700
1758
|
// A Next.js base path is fixed at build, so the login that serves `/sign-in` is a second app built from
|
|
@@ -1728,6 +1786,7 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1728
1786
|
await deps.coolify.updateApplication(signInApp.appId, {
|
|
1729
1787
|
custom_internal_name: signInNames.internal,
|
|
1730
1788
|
...IMAGE_HEALTHCHECK_SETTINGS,
|
|
1789
|
+
...gitBranchPatchFor(signInApp),
|
|
1731
1790
|
});
|
|
1732
1791
|
}
|
|
1733
1792
|
// ---- the broker app (#204) -------------------------------------------------
|
|
@@ -1753,6 +1812,7 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1753
1812
|
await deps.coolify.updateApplication(brokerAppId, {
|
|
1754
1813
|
custom_internal_name: target.internal.authBroker,
|
|
1755
1814
|
...IMAGE_HEALTHCHECK_SETTINGS,
|
|
1815
|
+
...gitBranchPatchFor(brokerApp),
|
|
1756
1816
|
});
|
|
1757
1817
|
// ---- env -----------------------------------------------------------------
|
|
1758
1818
|
// Generated once, handed straight to `bulkUpsertEnvs` and nowhere else --
|
|
@@ -1871,12 +1931,23 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1871
1931
|
await deps.coolify.bulkUpsertEnvs(brokerAppId, brokerEnvs);
|
|
1872
1932
|
// The in-app login: the login's env, the base path as a build argument, and the host table.
|
|
1873
1933
|
let signInEnvsChanged = false;
|
|
1934
|
+
// The session bind key is minted once, on the in-app login app only: a key already live (32 characters or
|
|
1935
|
+
// more) is kept, so a rerun after a crash mints nothing. It goes to this app's env and nowhere else.
|
|
1936
|
+
let bindKeyWritten = false;
|
|
1874
1937
|
if (signInApp && input.signInLogin) {
|
|
1875
1938
|
const bindings = input.signInLogin.bindings ??
|
|
1876
1939
|
(await currentEnvValue(deps.coolify, loginAppId, LOGIN_HOST_BINDINGS_ENV));
|
|
1940
|
+
const mintedBindKey = await ensureGeneratedEnv(deps.coolify, signInApp.appId, SIGN_IN_BIND_KEY_ENV, () => randomBytes(32).toString('hex'), SIGN_IN_BIND_KEY_MIN_LENGTH);
|
|
1941
|
+
const bindKey = mintedBindKey ?? (await currentEnvValue(deps.coolify, signInApp.appId, SIGN_IN_BIND_KEY_ENV));
|
|
1942
|
+
if (!bindKey) {
|
|
1943
|
+
throw new Error(`${label}: the in-app login app lost its session bind key between two reads`);
|
|
1944
|
+
}
|
|
1945
|
+
runtime.onSecret?.(bindKey);
|
|
1946
|
+
bindKeyWritten = mintedBindKey !== null;
|
|
1877
1947
|
const signInEnvs = [
|
|
1878
1948
|
...loginEnvs,
|
|
1879
1949
|
{ key: SIGN_IN_BUILD_ARG_ENV, value: SIGN_IN_BASE_PATH, buildTime: true },
|
|
1950
|
+
{ key: SIGN_IN_BIND_KEY_ENV, value: bindKey },
|
|
1880
1951
|
...(bindings ? [{ key: LOGIN_HOST_BINDINGS_ENV, value: bindings }] : []),
|
|
1881
1952
|
];
|
|
1882
1953
|
signInEnvsChanged = await envsDiffer(deps.coolify, signInApp.appId, signInEnvs);
|
|
@@ -1902,11 +1973,22 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1902
1973
|
await waitHealthy(withCallTimeout(deps.coolify), zitadelAppId, runtime, label, 'ZITADEL');
|
|
1903
1974
|
await record(SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY, '');
|
|
1904
1975
|
}
|
|
1905
|
-
if (loginApp.justCreated || !loginApp.running || loginEnvsChanged) {
|
|
1976
|
+
if (loginApp.justCreated || !loginApp.running || loginEnvsChanged || authRefChanged) {
|
|
1906
1977
|
await deps.coolify.deploy(loginAppId);
|
|
1907
1978
|
}
|
|
1908
|
-
|
|
1979
|
+
// The key counts as live only once the app was deployed after it was written: a crash between the write
|
|
1980
|
+
// and the deploy leaves the marker unset, so the next run deploys.
|
|
1981
|
+
const bindKeyUnconfirmed = Boolean(signInApp && input.signInLogin) && recorded[SIGN_IN_BIND_KEY_SCOPE_KEY] !== '1';
|
|
1982
|
+
if (signInApp &&
|
|
1983
|
+
(signInApp.justCreated ||
|
|
1984
|
+
!signInApp.running ||
|
|
1985
|
+
signInEnvsChanged ||
|
|
1986
|
+
bindKeyWritten ||
|
|
1987
|
+
bindKeyUnconfirmed ||
|
|
1988
|
+
authRefChanged)) {
|
|
1909
1989
|
await deps.coolify.deploy(signInApp.appId);
|
|
1990
|
+
if (input.signInLogin)
|
|
1991
|
+
await record(SIGN_IN_BIND_KEY_SCOPE_KEY, '1');
|
|
1910
1992
|
}
|
|
1911
1993
|
// #211: the broker's own `server.ts` refuses to start without
|
|
1912
1994
|
// `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` and `LETTERMINT_API_TOKEN` --
|
|
@@ -1929,6 +2011,7 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
1929
2011
|
(brokerApp.justCreated ||
|
|
1930
2012
|
!brokerApp.running ||
|
|
1931
2013
|
brokerEnvsChanged ||
|
|
2014
|
+
authRefChanged ||
|
|
1932
2015
|
brokerDeployNeeded(brokerMissingInputs, brokerAppId, authAppsRaw, recordedBrokerDeploy))) {
|
|
1933
2016
|
await deps.coolify.deploy(brokerAppId);
|
|
1934
2017
|
await record(PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY, brokerDeployRecordValue(brokerAppId, authAppsRaw));
|
|
@@ -2302,6 +2385,9 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
2302
2385
|
if (signInApp && (await currentEnvValue(deps.coolify, signInApp.appId, SERVICE_USER_TOKEN_ENV))) {
|
|
2303
2386
|
await record(SIGN_IN_LOGIN_APP_SCOPE_KEY, signInApp.appId);
|
|
2304
2387
|
}
|
|
2388
|
+
// Last, so a throw above runs the patch and the deploys again. Only the platform's own apps follow it.
|
|
2389
|
+
if (target.kind === 'platform')
|
|
2390
|
+
await record(PLATFORM_AUTH_APPLIED_GIT_REF_SCOPE_KEY, gitRef);
|
|
2305
2391
|
// ---- the owner company's own sign-in org ----------------------------------
|
|
2306
2392
|
// Archon signs people in from this org; the `foundry` org holds none. The
|
|
2307
2393
|
// company executor finds the same org by name (its slug) and makes no second.
|
|
@@ -2423,7 +2509,8 @@ const platformAuthProject = {
|
|
|
2423
2509
|
},
|
|
2424
2510
|
};
|
|
2425
2511
|
/** The platform's sign-in: {@link provisionAuthStack} with {@link platformTarget} and the platform's own inputs. */
|
|
2426
|
-
async function provisionPlatformAuth(context, deps,
|
|
2512
|
+
async function provisionPlatformAuth(context, deps, options = {}) {
|
|
2513
|
+
const { ownerCompany = null, signInLogin = false } = options;
|
|
2427
2514
|
const { platform } = context;
|
|
2428
2515
|
const scope = scopeFromPlatform(context);
|
|
2429
2516
|
return provisionAuthStack(scope, deps, platformTarget(), {
|
|
@@ -2542,6 +2629,74 @@ async function provisionPlatformArchonOrg(context, deps, ownerCompany) {
|
|
|
2542
2629
|
}
|
|
2543
2630
|
return {};
|
|
2544
2631
|
}
|
|
2632
|
+
/**
|
|
2633
|
+
* #394 (decision D2, release one): makes the role {@link PLATFORM_MAIL_USER_ROLE} on the mail
|
|
2634
|
+
* project and grants it to the platform owner (`zitadel:mail-role` keyed {@link PLATFORM_MAIL_KEY}).
|
|
2635
|
+
* Each marker is recorded right after its call, so a failure leaves the earlier ones and the next
|
|
2636
|
+
* plan names the step again. The role check is turned on only when the host asked for it and the
|
|
2637
|
+
* grant was recorded before this run began, never in the run that made the grant. A grant is made
|
|
2638
|
+
* only when the owner is in the mail project's own org (`planMailRole` reports the other case).
|
|
2639
|
+
* The error names ids only, never the cause.
|
|
2640
|
+
*/
|
|
2641
|
+
async function applyPlatformMailRole(context, deps) {
|
|
2642
|
+
const { platform } = context;
|
|
2643
|
+
const recorded = context.recordedIds();
|
|
2644
|
+
const orgId = recorded[PLATFORM_ZITADEL_ORG_SCOPE_KEY];
|
|
2645
|
+
const projectId = recorded[PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY];
|
|
2646
|
+
const invite = recorded[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY];
|
|
2647
|
+
if (!orgId || !projectId || !invite) {
|
|
2648
|
+
throw new Error("platform:mail: the sign-in's ZITADEL org, the mail project or the owner invite is not recorded yet -- cannot make the mail role");
|
|
2649
|
+
}
|
|
2650
|
+
const zitadel = deps.zitadelForInstance?.(`https://${platform.auth.hostname}`) ?? deps.zitadel;
|
|
2651
|
+
// The planner plans nothing without the methods; a stale plan does nothing.
|
|
2652
|
+
if (!zitadel.ensureProjectRole || !zitadel.grantProjectRoles)
|
|
2653
|
+
return {};
|
|
2654
|
+
const record = async (key, value) => {
|
|
2655
|
+
context.recordScopeId(key, value);
|
|
2656
|
+
await context.persist?.();
|
|
2657
|
+
};
|
|
2658
|
+
const grantedBefore = recorded[PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY] === `${projectId}|${invite}`;
|
|
2659
|
+
if (recorded[PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY] !== projectId) {
|
|
2660
|
+
try {
|
|
2661
|
+
await zitadel.ensureProjectRole(orgId, projectId, {
|
|
2662
|
+
key: PLATFORM_MAIL_USER_ROLE,
|
|
2663
|
+
displayName: 'Mail user',
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
catch {
|
|
2667
|
+
throw new Error(`platform:mail: making the mail role failed on project ${projectId}`);
|
|
2668
|
+
}
|
|
2669
|
+
await record(PLATFORM_MAIL_ROLE_ENSURED_SCOPE_KEY, projectId);
|
|
2670
|
+
}
|
|
2671
|
+
const sameOrg = invite === 'none' || recorded[PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY] === orgId;
|
|
2672
|
+
if (!sameOrg)
|
|
2673
|
+
return {};
|
|
2674
|
+
if (!grantedBefore) {
|
|
2675
|
+
if (invite !== 'none') {
|
|
2676
|
+
try {
|
|
2677
|
+
await zitadel.grantProjectRoles(orgId, invite, projectId, [PLATFORM_MAIL_USER_ROLE]);
|
|
2678
|
+
}
|
|
2679
|
+
catch {
|
|
2680
|
+
throw new Error(`platform:mail: granting the mail role to user ${invite} failed on project ${projectId}`);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
await record(PLATFORM_MAIL_ROLE_GRANTED_SCOPE_KEY, `${projectId}|${invite}`);
|
|
2684
|
+
return {};
|
|
2685
|
+
}
|
|
2686
|
+
if (deps.requireMailRole && zitadel.setProjectRoleChecks) {
|
|
2687
|
+
try {
|
|
2688
|
+
await zitadel.setProjectRoleChecks(orgId, projectId, {
|
|
2689
|
+
projectRoleCheck: true,
|
|
2690
|
+
hasProjectCheck: true,
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
catch {
|
|
2694
|
+
throw new Error(`platform:mail: turning on the mail role check failed on project ${projectId}`);
|
|
2695
|
+
}
|
|
2696
|
+
await record(PLATFORM_MAIL_ROLE_CHECK_SCOPE_KEY, projectId);
|
|
2697
|
+
}
|
|
2698
|
+
return {};
|
|
2699
|
+
}
|
|
2545
2700
|
/**
|
|
2546
2701
|
* Reserves the username `admin` and switches self-registration off in the
|
|
2547
2702
|
* platform's sign-in org (`zitadel:sign-in-policy` keyed {@link PLATFORM_AUTH_KEY}).
|
|
@@ -3829,6 +3984,76 @@ async function switchOnArchonIdTokenUserinfo(context, deps) {
|
|
|
3829
3984
|
await context.persist?.();
|
|
3830
3985
|
return {};
|
|
3831
3986
|
}
|
|
3987
|
+
/**
|
|
3988
|
+
* Replaces the post-logout addresses of Archon's sign-in client
|
|
3989
|
+
* (`zitadel:post-logout-uris` keyed {@link PLATFORM_ARCHON_KEY}). The marker
|
|
3990
|
+
* (app id and set) is recorded only after the call returned, so a call that
|
|
3991
|
+
* throws leaves none and the next plan names the step again. The error names
|
|
3992
|
+
* the ids only, never the cause.
|
|
3993
|
+
*/
|
|
3994
|
+
async function updateArchonPostLogoutUris(context, deps) {
|
|
3995
|
+
const recorded = context.recordedIds();
|
|
3996
|
+
const ownerSignIn = context.owner?.signIn ?? null;
|
|
3997
|
+
const zitadel = ownerSignIn
|
|
3998
|
+
? deps.zitadelForInstance?.(ownerSignIn.issuerUrl, ownerSignIn.bearerRef)
|
|
3999
|
+
: undefined;
|
|
4000
|
+
// The planner plans nothing without the method; a stale plan does nothing.
|
|
4001
|
+
if (!ownerSignIn || !zitadel?.updateOidcAppPostLogoutRedirectUris)
|
|
4002
|
+
return {};
|
|
4003
|
+
const orgId = recorded[PLATFORM_ARCHON_CLIENT_ORG_SCOPE_KEY] ?? ownerSignIn.organisationId;
|
|
4004
|
+
const projectId = recorded[PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY];
|
|
4005
|
+
const appId = recorded[PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY];
|
|
4006
|
+
if (!orgId || !projectId || !appId) {
|
|
4007
|
+
throw new Error('platform:archon: the sign-in client is not recorded yet -- cannot update its post-logout addresses');
|
|
4008
|
+
}
|
|
4009
|
+
const uris = postLogoutUrisFor([context.platform.archon.hostname]);
|
|
4010
|
+
try {
|
|
4011
|
+
await zitadel.updateOidcAppPostLogoutRedirectUris(orgId, projectId, appId, uris);
|
|
4012
|
+
}
|
|
4013
|
+
catch {
|
|
4014
|
+
throw new Error(`platform:archon: updating the post-logout addresses failed for the sign-in client ${appId}`);
|
|
4015
|
+
}
|
|
4016
|
+
context.recordScopeId(PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, postLogoutAppliedValue(appId, uris));
|
|
4017
|
+
await context.persist?.();
|
|
4018
|
+
return {};
|
|
4019
|
+
}
|
|
4020
|
+
/**
|
|
4021
|
+
* #339: registers `/auth/desktop/callback` on Archon's sign-in client
|
|
4022
|
+
* (`zitadel:desktop-callback` keyed {@link PLATFORM_ARCHON_KEY}), keeping the
|
|
4023
|
+
* redirect URIs it holds. The marker is the client's app id, recorded only
|
|
4024
|
+
* after the read and the write resolved. The error names the ids only.
|
|
4025
|
+
*/
|
|
4026
|
+
async function registerArchonDesktopCallback(context, deps) {
|
|
4027
|
+
const recorded = context.recordedIds();
|
|
4028
|
+
const ownerSignIn = context.owner?.signIn ?? null;
|
|
4029
|
+
const zitadel = ownerSignIn
|
|
4030
|
+
? deps.zitadelForInstance?.(ownerSignIn.issuerUrl, ownerSignIn.bearerRef)
|
|
4031
|
+
: undefined;
|
|
4032
|
+
// The planner plans nothing without the methods; a stale plan does nothing.
|
|
4033
|
+
if (!ownerSignIn || !zitadel?.getOidcAppRedirectUris || !zitadel.updateOidcAppRedirectUris) {
|
|
4034
|
+
return {};
|
|
4035
|
+
}
|
|
4036
|
+
const orgId = ownerSignIn.organisationId;
|
|
4037
|
+
const projectId = recorded[PLATFORM_ARCHON_ZITADEL_PROJECT_SCOPE_KEY];
|
|
4038
|
+
const appId = recorded[PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY];
|
|
4039
|
+
const hostname = recorded[PLATFORM_ARCHON_CLIENT_HOSTNAME_SCOPE_KEY];
|
|
4040
|
+
if (!orgId || !projectId || !appId || !hostname) {
|
|
4041
|
+
throw new Error('platform:archon: the sign-in client is not recorded yet -- cannot register its desktop callback');
|
|
4042
|
+
}
|
|
4043
|
+
try {
|
|
4044
|
+
const current = await zitadel.getOidcAppRedirectUris(orgId, projectId, appId);
|
|
4045
|
+
const union = unionRedirectUris(current, [desktopCallbackUri(hostname)]);
|
|
4046
|
+
if (union.length > current.length) {
|
|
4047
|
+
await zitadel.updateOidcAppRedirectUris(orgId, projectId, appId, union);
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
catch {
|
|
4051
|
+
throw new Error(`platform:archon: registering the desktop callback failed for the sign-in client ${appId}`);
|
|
4052
|
+
}
|
|
4053
|
+
context.recordScopeId(PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, appId);
|
|
4054
|
+
await context.persist?.();
|
|
4055
|
+
return {};
|
|
4056
|
+
}
|
|
3832
4057
|
/**
|
|
3833
4058
|
* #175: makes `coolify:app` keyed {@link PLATFORM_ARCHON_KEY} true: Archon
|
|
3834
4059
|
* from https://github.com/wtfalch/archon on `platform.archon.gitRef`, its own
|
|
@@ -4130,12 +4355,15 @@ async function provisionPlatformArchon(context, deps, ownerCompany = null) {
|
|
|
4130
4355
|
appType: 'web',
|
|
4131
4356
|
// `@wtfalch/auth`'s default callback path (Archon's own
|
|
4132
4357
|
// `docs/operator-deployment.md`, step 4).
|
|
4133
|
-
redirectUris: [`https://${hostname}/auth/callback
|
|
4134
|
-
postLogoutRedirectUris: [
|
|
4358
|
+
redirectUris: [`https://${hostname}/auth/callback`, desktopCallbackUri(hostname)],
|
|
4359
|
+
postLogoutRedirectUris: postLogoutUrisFor([hostname]),
|
|
4135
4360
|
});
|
|
4136
4361
|
context.recordScopeId(PLATFORM_ARCHON_ZITADEL_APP_SCOPE_KEY, created.appId);
|
|
4362
|
+
context.recordScopeId(PLATFORM_ARCHON_POST_LOGOUT_APPLIED_SCOPE_KEY, postLogoutAppliedValue(created.appId, postLogoutUrisFor([hostname])));
|
|
4137
4363
|
// Made with the setting on (providers 0.9.0): nothing to repair on this id.
|
|
4138
4364
|
context.recordScopeId(PLATFORM_ARCHON_ID_TOKEN_USERINFO_APPLIED_SCOPE_KEY, created.appId);
|
|
4365
|
+
// Made with the desktop callback (#339): nothing to repair on this id either.
|
|
4366
|
+
context.recordScopeId(PLATFORM_ARCHON_DESKTOP_CALLBACK_APPLIED_SCOPE_KEY, created.appId);
|
|
4139
4367
|
context.recordScopeId(PLATFORM_ARCHON_CLIENT_HOSTNAME_SCOPE_KEY, hostname);
|
|
4140
4368
|
context.recordScopeId(PLATFORM_ARCHON_CLIENT_ORG_SCOPE_KEY, org.id);
|
|
4141
4369
|
await record(PLATFORM_ARCHON_CLIENT_ID_SCOPE_KEY, created.clientId);
|
|
@@ -4654,14 +4882,27 @@ async function provisionPlatformArchon(context, deps, ownerCompany = null) {
|
|
|
4654
4882
|
if (!archonDeployedNow || !selfRedeploy) {
|
|
4655
4883
|
if (inAppWanted) {
|
|
4656
4884
|
await archonInAppPoint({ ...inAppTarget, zitadel: inAppCalls() });
|
|
4885
|
+
// A changed Archon host: the old address's binding and trusted domain go once the client points at the new one.
|
|
4886
|
+
const previousHost = archonInAppHost(context.recordedIds());
|
|
4887
|
+
if (previousHost !== '' && previousHost !== hostname && ownerSignIn.signInLoginAppId) {
|
|
4888
|
+
await archonInAppUnbind({
|
|
4889
|
+
...inAppTarget,
|
|
4890
|
+
host: previousHost,
|
|
4891
|
+
zitadel: inAppCalls(),
|
|
4892
|
+
loginAppId: ownerSignIn.signInLoginAppId,
|
|
4893
|
+
});
|
|
4894
|
+
}
|
|
4657
4895
|
await record(PLATFORM_ARCHON_IN_APP_SCOPE_KEY, `${hostname}=${clientId}`);
|
|
4658
4896
|
}
|
|
4659
4897
|
else if (inAppOwed) {
|
|
4898
|
+
if (!ownerSignIn.signInLoginAppId) {
|
|
4899
|
+
throw new Error("platform:archon: Archon's sign-in address cannot be given up without the owner company's in-app login app");
|
|
4900
|
+
}
|
|
4660
4901
|
await archonInAppUnbind({
|
|
4661
4902
|
...inAppTarget,
|
|
4662
4903
|
host: archonInAppHost(context.recordedIds()),
|
|
4663
4904
|
zitadel: inAppCalls(),
|
|
4664
|
-
|
|
4905
|
+
loginAppId: ownerSignIn.signInLoginAppId,
|
|
4665
4906
|
});
|
|
4666
4907
|
await record(PLATFORM_ARCHON_IN_APP_SCOPE_KEY, '');
|
|
4667
4908
|
}
|
|
@@ -5454,6 +5695,32 @@ async function archonDeploymentState(coolify, deploymentUuid) {
|
|
|
5454
5695
|
return isNotFoundError(error) ? 'failed' : 'unreadable';
|
|
5455
5696
|
}
|
|
5456
5697
|
}
|
|
5698
|
+
/**
|
|
5699
|
+
* #643: whether a recorded sign-in app (ZITADEL, login or broker) is gone from Coolify or not running.
|
|
5700
|
+
* The same status test the step's own deploy decision uses. A read that fails for any other reason
|
|
5701
|
+
* counts as running, with one fixed log line (the error can carry values).
|
|
5702
|
+
*/
|
|
5703
|
+
async function authAppsNotRunning(coolify, recordedIds) {
|
|
5704
|
+
for (const scopeKey of [
|
|
5705
|
+
PLATFORM_AUTH_ZITADEL_SCOPE_KEY,
|
|
5706
|
+
PLATFORM_AUTH_LOGIN_SCOPE_KEY,
|
|
5707
|
+
PLATFORM_AUTH_BROKER_SCOPE_KEY,
|
|
5708
|
+
]) {
|
|
5709
|
+
const appId = recordedIds[scopeKey];
|
|
5710
|
+
if (!appId)
|
|
5711
|
+
continue;
|
|
5712
|
+
try {
|
|
5713
|
+
if (!isRunningOrStarting((await coolify.getApplication(appId)).status))
|
|
5714
|
+
return true;
|
|
5715
|
+
}
|
|
5716
|
+
catch (error) {
|
|
5717
|
+
if (isNotFoundError(error))
|
|
5718
|
+
return true;
|
|
5719
|
+
console.error('platform:auth: could not read a sign-in app status; treating it as running');
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
return false;
|
|
5723
|
+
}
|
|
5457
5724
|
async function archonRollingUpdateDrifted(coolify, appId) {
|
|
5458
5725
|
if (!appId)
|
|
5459
5726
|
return false;
|
|
@@ -6638,7 +6905,10 @@ async function findOrCreateSignInLoginApp(coolify, input) {
|
|
|
6638
6905
|
if (app.environment_id !== environment.id) {
|
|
6639
6906
|
throw new Error(`${label}: an application named ${name} exists outside the ${environment.name} environment of the sign-in project; foundry does not adopt it`);
|
|
6640
6907
|
}
|
|
6641
|
-
if (app.git_repository
|
|
6908
|
+
if (app.git_repository === undefined) {
|
|
6909
|
+
throw new Error(`${label}: the application ${name} has no git repository, so foundry cannot tell it is built from ${AUTH_REPO} and does not adopt it`);
|
|
6910
|
+
}
|
|
6911
|
+
if (!app.git_repository.endsWith(AUTH_REPO)) {
|
|
6642
6912
|
throw new Error(`${label}: the application ${name} is built from ${app.git_repository}, not ${AUTH_REPO}; foundry does not adopt it`);
|
|
6643
6913
|
}
|
|
6644
6914
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DeclaredOrg } from '../org.js';
|
|
2
|
+
/** The page `@wtfalch/auth` apps land on after ZITADEL ends the session. */
|
|
3
|
+
export declare const SIGNED_OUT_PATH = "/auth/signed-out";
|
|
4
|
+
/** Both landing addresses of a host: its root and its signed-out page. */
|
|
5
|
+
export declare function postLogoutUrisFor(hosts: readonly string[]): string[];
|
|
6
|
+
/**
|
|
7
|
+
* The marker value for an app: its ZITADEL id and the set it holds, in a fixed
|
|
8
|
+
* order. A changed set or a re-created app gives a different value, so the
|
|
9
|
+
* next plan names the app again.
|
|
10
|
+
*/
|
|
11
|
+
export declare function postLogoutAppliedValue(appId: string, uris: readonly string[]): string;
|
|
12
|
+
/** True while the recorded marker is not the one the app holds with `uris`. */
|
|
13
|
+
export declare function postLogoutUpdateOwed(recordedMarker: string | undefined, appId: string, uris: readonly string[]): boolean;
|
|
14
|
+
/** The scope id holding company app `appKey`'s applied post-logout marker. */
|
|
15
|
+
export declare function zitadelAppPostLogoutAppliedScopeKey(appKey: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
18
|
+
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
19
|
+
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
20
|
+
* `uses.auth`) holds both on each of its hosts.
|
|
21
|
+
*/
|
|
22
|
+
export declare function companyAppPostLogoutUris(org: DeclaredOrg, appKey: string): string[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { agoraHostnameFor } from './agora.js';
|
|
2
|
+
import { bouleHostnameFor } from './boule.js';
|
|
3
|
+
import { appHostname, cmsStudioHostnameFor, entryHostnames } from './hostnames.js';
|
|
4
|
+
/** The page `@wtfalch/auth` apps land on after ZITADEL ends the session. */
|
|
5
|
+
export const SIGNED_OUT_PATH = '/auth/signed-out';
|
|
6
|
+
/** Both landing addresses of a host: its root and its signed-out page. */
|
|
7
|
+
export function postLogoutUrisFor(hosts) {
|
|
8
|
+
return hosts.flatMap((host) => [`https://${host}/`, `https://${host}${SIGNED_OUT_PATH}`]);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The marker value for an app: its ZITADEL id and the set it holds, in a fixed
|
|
12
|
+
* order. A changed set or a re-created app gives a different value, so the
|
|
13
|
+
* next plan names the app again.
|
|
14
|
+
*/
|
|
15
|
+
export function postLogoutAppliedValue(appId, uris) {
|
|
16
|
+
return `${appId} ${[...uris].sort().join(' ')}`;
|
|
17
|
+
}
|
|
18
|
+
/** True while the recorded marker is not the one the app holds with `uris`. */
|
|
19
|
+
export function postLogoutUpdateOwed(recordedMarker, appId, uris) {
|
|
20
|
+
return recordedMarker !== postLogoutAppliedValue(appId, uris);
|
|
21
|
+
}
|
|
22
|
+
/** The scope id holding company app `appKey`'s applied post-logout marker. */
|
|
23
|
+
export function zitadelAppPostLogoutAppliedScopeKey(appKey) {
|
|
24
|
+
return `zitadel_app_post_logout_applied.${appKey}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The post-logout addresses company app `appKey` is created with and kept at.
|
|
28
|
+
* Agora signs out locally and has no signed-out page, so it holds only its
|
|
29
|
+
* root; every `@wtfalch/auth` app (cms, boule, the app, entries with
|
|
30
|
+
* `uses.auth`) holds both on each of its hosts.
|
|
31
|
+
*/
|
|
32
|
+
export function companyAppPostLogoutUris(org, appKey) {
|
|
33
|
+
const authEntry = org.hosting.entries.find((e) => e.key === appKey && e.uses?.auth);
|
|
34
|
+
const hosts = authEntry ? entryHostnames(org, authEntry) : null;
|
|
35
|
+
if (appKey === 'agora')
|
|
36
|
+
return [`https://${agoraHostnameFor(org)}/`];
|
|
37
|
+
const hostname = appKey === 'cms' && org.site.enabled
|
|
38
|
+
? cmsStudioHostnameFor(org)
|
|
39
|
+
: appKey === 'boule'
|
|
40
|
+
? bouleHostnameFor(org)
|
|
41
|
+
: appHostname(org);
|
|
42
|
+
return postLogoutUrisFor(hosts ?? [hostname]);
|
|
43
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { CoolifyClient } from '@wtfalch/providers/coolify';
|
|
|
3
3
|
import { type ZitadelClient } from '@wtfalch/providers/zitadel';
|
|
4
4
|
import { type OrgId } from '../org.js';
|
|
5
5
|
import type { DeclaredOrgStore } from '../store.js';
|
|
6
|
-
import type
|
|
6
|
+
import { type AiReader, type CloudflareReader, type CmsReader, type CoolifyReader, type LiveResource, type ZitadelReader } from './live.js';
|
|
7
7
|
/** The `CoolifyClient` methods the readers call, so a host's own implementation needs no cast. */
|
|
8
8
|
type CoolifyReaderClient = Pick<CoolifyClient, 'listResources' | 'getApplication' | 'getDatabase' | 'listEnvs' | 'listStorages'>;
|
|
9
9
|
/**
|
|
@@ -152,6 +152,15 @@ export declare class ProvidersZitadelReader implements ZitadelReader {
|
|
|
152
152
|
private readonly resolveLoginHost;
|
|
153
153
|
constructor(client: ZitadelClient, resolveScope: ScopeResolver, resolveRecordedIds: RecordedIds, resolveLoginHost?: ScopeResolver);
|
|
154
154
|
listResources(orgId: OrgId): Promise<LiveResource[]>;
|
|
155
|
+
/**
|
|
156
|
+
* #557: a recorded app the listing does not return, proven gone by a direct
|
|
157
|
+
* read by app id in every project the listing shows. One `app-gone`
|
|
158
|
+
* resource per proven app, keyed by the app's name: `diff.ts` offers the
|
|
159
|
+
* replacement only for it. Nothing is proven when the client has no
|
|
160
|
+
* `getApp` (providers before 0.12.0), when the read throws, or when it
|
|
161
|
+
* finds the app, so the plan blocks instead.
|
|
162
|
+
*/
|
|
163
|
+
private goneApps;
|
|
155
164
|
/**
|
|
156
165
|
* #510: `client.listResources` reports only what is *inside* a ZITADEL org
|
|
157
166
|
* (its projects and grants), never the org record itself, so nothing ever
|
|
@@ -2,6 +2,7 @@ import { ZitadelError } from '@wtfalch/providers/zitadel';
|
|
|
2
2
|
import { APP_ENTRY_KEY } from '../org.js';
|
|
3
3
|
import { BOULE_MAIL_TOKEN_ENV, BOULE_MANAGED_ENV_KEYS } from './boule.js';
|
|
4
4
|
import { previewsOn } from './coolify-previews.js';
|
|
5
|
+
import { APP_GONE_KIND, } from './live.js';
|
|
5
6
|
import { SIGN_IN_UPSTREAM_ENV } from './sign-in.js';
|
|
6
7
|
/**
|
|
7
8
|
* A {@link ScopeResolver} backed by `AppliedState.serviceIds` (`../state.ts`)
|
|
@@ -416,7 +417,46 @@ export class ProvidersZitadelReader {
|
|
|
416
417
|
this.liveIssuer(orgId, zitadelOrgId),
|
|
417
418
|
]);
|
|
418
419
|
const mapped = applyRecordedIds(toFoundryResources('zitadel', resources), recorded);
|
|
419
|
-
|
|
420
|
+
const gone = await this.goneApps(zitadelOrgId, resources, recorded);
|
|
421
|
+
return issuer ? [...mapped, ...gone, issuer] : [...mapped, ...gone];
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* #557: a recorded app the listing does not return, proven gone by a direct
|
|
425
|
+
* read by app id in every project the listing shows. One `app-gone`
|
|
426
|
+
* resource per proven app, keyed by the app's name: `diff.ts` offers the
|
|
427
|
+
* replacement only for it. Nothing is proven when the client has no
|
|
428
|
+
* `getApp` (providers before 0.12.0), when the read throws, or when it
|
|
429
|
+
* finds the app, so the plan blocks instead.
|
|
430
|
+
*/
|
|
431
|
+
async goneApps(zitadelOrgId, listed, recorded) {
|
|
432
|
+
// A host may pass a client older than providers 0.12.0, which has no `getApp`.
|
|
433
|
+
const getApp = typeof this.client.getApp === 'function' ? this.client.getApp.bind(this.client) : undefined;
|
|
434
|
+
const listedIds = new Set(listed.filter((r) => r.kind === 'app').map((r) => r.key));
|
|
435
|
+
const projectIds = listed.filter((r) => r.kind === 'project').map((r) => r.key);
|
|
436
|
+
if (!getApp || projectIds.length === 0)
|
|
437
|
+
return [];
|
|
438
|
+
const gone = [];
|
|
439
|
+
for (const [appId, resourceKey] of Object.entries(recorded)) {
|
|
440
|
+
if (!resourceKey.startsWith('app:') || listedIds.has(appId))
|
|
441
|
+
continue;
|
|
442
|
+
try {
|
|
443
|
+
const found = await Promise.all(projectIds.map((projectId) => getApp(zitadelOrgId, projectId, appId)));
|
|
444
|
+
if (found.every((app) => app === null || app.state === 'APP_STATE_REMOVED')) {
|
|
445
|
+
gone.push({
|
|
446
|
+
system: 'zitadel',
|
|
447
|
+
kind: APP_GONE_KIND,
|
|
448
|
+
key: resourceKey.slice('app:'.length),
|
|
449
|
+
config: {},
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
// Unproven, not gone: the plan blocks this app. Only the error's name is kept.
|
|
455
|
+
if (!(error instanceof ZitadelError))
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return gone;
|
|
420
460
|
}
|
|
421
461
|
/**
|
|
422
462
|
* #510: `client.listResources` reports only what is *inside* a ZITADEL org
|
|
@@ -13,4 +13,8 @@ export declare function editSignInBindings(coolify: Pick<CoolifyPort, 'listEnvs'
|
|
|
13
13
|
instanceHost: string;
|
|
14
14
|
upsert?: readonly LoginHostBinding[];
|
|
15
15
|
remove?: readonly string[];
|
|
16
|
+
clientOrganizations?: {
|
|
17
|
+
upsert?: Readonly<Record<string, string>>;
|
|
18
|
+
remove?: readonly string[];
|
|
19
|
+
};
|
|
16
20
|
}): Promise<void>;
|