@wtfalch/foundry 0.16.12 → 0.16.14
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.
|
@@ -108,7 +108,7 @@ export function guardStepDeps(executor, redactor, deliverSecret, heldKeys) {
|
|
|
108
108
|
// #201: ai's own default minter needs heldKeys too, to re-sign a mint
|
|
109
109
|
// request against its signing key when an earlier run pinned it but
|
|
110
110
|
// crashed before the management key itself was minted and delivered.
|
|
111
|
-
if (key === 'archon' || key === 'ai' || 'heldKeys' in copy)
|
|
111
|
+
if (key === 'archon' || key === 'ai' || key === 'auth' || 'heldKeys' in copy)
|
|
112
112
|
copy.heldKeys = heldKeys;
|
|
113
113
|
out[key] = copy;
|
|
114
114
|
}
|
|
@@ -125,6 +125,18 @@ export declare const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = "archon_applied_
|
|
|
125
125
|
* between is repaired by the next one.
|
|
126
126
|
*/
|
|
127
127
|
export declare const PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY = "archon_gate_closed";
|
|
128
|
+
/**
|
|
129
|
+
* The ZITADEL SMTP provider (Lettermint) `provisionPlatformAuth` configured on
|
|
130
|
+
* the platform's instance, recorded as the provider id. Without it ZITADEL
|
|
131
|
+
* cannot send the owner's invite, so a platform applied before it existed
|
|
132
|
+
* plans the auth step again.
|
|
133
|
+
*/
|
|
134
|
+
export declare const PLATFORM_AUTH_SMTP_SCOPE_KEY = "zitadel_platform_smtp_provider_id";
|
|
135
|
+
/**
|
|
136
|
+
* The platform owner's ZITADEL user id, recorded once the invite email was
|
|
137
|
+
* requested, so the invite goes out once. `none` when no owner is declared.
|
|
138
|
+
*/
|
|
139
|
+
export declare const PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY = "zitadel_platform_owner_invite_user_id";
|
|
128
140
|
export declare const PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY = "zitadel_platform_admin_user_id";
|
|
129
141
|
export declare const PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY = "zitadel_platform_admin_user_undelivered";
|
|
130
142
|
/** #283: the created instance's id, recorded once its owner token is delivered. */
|
|
@@ -145,6 +145,18 @@ export const PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY = 'archon_applied_broker_a
|
|
|
145
145
|
* between is repaired by the next one.
|
|
146
146
|
*/
|
|
147
147
|
export const PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY = 'archon_gate_closed';
|
|
148
|
+
/**
|
|
149
|
+
* The ZITADEL SMTP provider (Lettermint) `provisionPlatformAuth` configured on
|
|
150
|
+
* the platform's instance, recorded as the provider id. Without it ZITADEL
|
|
151
|
+
* cannot send the owner's invite, so a platform applied before it existed
|
|
152
|
+
* plans the auth step again.
|
|
153
|
+
*/
|
|
154
|
+
export const PLATFORM_AUTH_SMTP_SCOPE_KEY = 'zitadel_platform_smtp_provider_id';
|
|
155
|
+
/**
|
|
156
|
+
* The platform owner's ZITADEL user id, recorded once the invite email was
|
|
157
|
+
* requested, so the invite goes out once. `none` when no owner is declared.
|
|
158
|
+
*/
|
|
159
|
+
export const PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY = 'zitadel_platform_owner_invite_user_id';
|
|
148
160
|
export const PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY = 'zitadel_platform_admin_user_id';
|
|
149
161
|
export const PLATFORM_AUTH_ADMIN_UNDELIVERED_SCOPE_KEY = 'zitadel_platform_admin_user_undelivered';
|
|
150
162
|
/** #283: the created instance's id, recorded once its owner token is delivered. */
|
|
@@ -1021,7 +1033,7 @@ function planSfu(platform) {
|
|
|
1021
1033
|
* #211 round 2: a recorded broker id is not the same as a *deployed* broker
|
|
1022
1034
|
* ({@link PLATFORM_AUTH_BROKER_DEPLOYED_SCOPE_KEY}) -- `provisionPlatformAuth`
|
|
1023
1035
|
* creates and records the app before it has `ZITADEL_SERVICE_USER_TOKEN`/
|
|
1024
|
-
* `
|
|
1036
|
+
* `LETTERMINT_API_TOKEN` to deploy it with (#209, #210). So this still plans the
|
|
1025
1037
|
* step again while the broker sits undeployed: `executorBlocked` (from
|
|
1026
1038
|
* `authBrokerBlockedReason`) reports it blocked, by name, for as long as the
|
|
1027
1039
|
* inputs stay missing, and the moment they arrive the same replanned step
|
|
@@ -1131,6 +1143,10 @@ function planAuth(platform, recordedIds, executorBlocked, executorMissing, authD
|
|
|
1131
1143
|
brokerApplied &&
|
|
1132
1144
|
brokerDeployed &&
|
|
1133
1145
|
loginClientApplied &&
|
|
1146
|
+
// Mail and the owner's invite: a platform applied before they existed
|
|
1147
|
+
// plans the step again, which only adds them.
|
|
1148
|
+
Boolean(recordedIds[PLATFORM_AUTH_SMTP_SCOPE_KEY]) &&
|
|
1149
|
+
Boolean(recordedIds[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY]) &&
|
|
1134
1150
|
// The ZITADEL run options reach the container only through the auth
|
|
1135
1151
|
// step's redeploy, so a platform recorded before them still plans it.
|
|
1136
1152
|
recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS) {
|
|
@@ -13,13 +13,32 @@ import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
|
13
13
|
* positions, so every #167-era caller of {@link FoundryPlatformStepExecutor}
|
|
14
14
|
* (zone only) keeps compiling with this third argument simply omitted.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* The SMTP call `provisionPlatformAuth` makes on the instance's ZITADEL.
|
|
18
|
+
* `@wtfalch/providers` gains it in 0.7.2; stated here so foundry compiles
|
|
19
|
+
* against 0.7.1 and tests use fakes.
|
|
20
|
+
*/
|
|
21
|
+
export interface PlatformZitadelSmtp {
|
|
22
|
+
ensureSmtpProvider(config: {
|
|
23
|
+
host: string;
|
|
24
|
+
user: string;
|
|
25
|
+
password: string;
|
|
26
|
+
senderAddress: string;
|
|
27
|
+
senderName: string;
|
|
28
|
+
tls: boolean;
|
|
29
|
+
description: string;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
id: string;
|
|
32
|
+
changed: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
16
35
|
export interface PlatformAuthDeps {
|
|
17
36
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
|
|
18
37
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'
|
|
19
38
|
/** #197: fetches the tunnel's connector token for {@link ensureCloudflaredConnector}. */
|
|
20
39
|
| 'getTunnelToken'>;
|
|
21
40
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
22
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats'
|
|
41
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'inviteOrgOwner'> & PlatformZitadelSmtp;
|
|
23
42
|
/**
|
|
24
43
|
* Builds the ZITADEL port for the new instance from its issuer URL
|
|
25
44
|
* (`https://<auth.hostname>`). When set it is used instead of
|
|
@@ -28,6 +47,19 @@ export interface PlatformAuthDeps {
|
|
|
28
47
|
*/
|
|
29
48
|
zitadelForInstance?: (issuerUrl: string) => PlatformAuthDeps['zitadel'];
|
|
30
49
|
deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
|
|
50
|
+
/**
|
|
51
|
+
* #210: reads back the broker's Lettermint mail key, held under
|
|
52
|
+
* {@link PLATFORM_AUTH_BROKER_MAIL_KEY_REF}. Never minted here -- unlike
|
|
53
|
+
* the login-client token above, foundry has no Lettermint account of its
|
|
54
|
+
* own to mint one from, so this only ever reads back a value a human (or
|
|
55
|
+
* Archon's own onboarding) put there directly once Lettermint issued it.
|
|
56
|
+
* Absent, or with nothing held under that ref yet, `LETTERMINT_API_TOKEN`
|
|
57
|
+
* stays a gap `missingBrokerInputs` reports by name, the same "blocked,
|
|
58
|
+
* not failing" every other unmet broker input already follows.
|
|
59
|
+
*/
|
|
60
|
+
heldKeys?: {
|
|
61
|
+
resolve(ref: string): Promise<string | null>;
|
|
62
|
+
};
|
|
31
63
|
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
32
64
|
runtime?: PlatformAuthRuntime;
|
|
33
65
|
}
|
|
@@ -185,7 +217,7 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
185
217
|
private readonly ai?;
|
|
186
218
|
private readonly archon?;
|
|
187
219
|
private readonly backup?;
|
|
188
|
-
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list'>, options?: FoundryPlatformStepExecutorOptions);
|
|
220
|
+
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list' | 'get'>, options?: FoundryPlatformStepExecutorOptions);
|
|
189
221
|
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
190
222
|
auth?: string;
|
|
191
223
|
authExecutorMissing?: boolean;
|
|
@@ -234,6 +266,15 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
234
266
|
* key delivered under `zitadel:platform:admin`.
|
|
235
267
|
*/
|
|
236
268
|
export declare const PLATFORM_AUTH_INSTANCE_BEARER_REF = "zitadel:platform:instanceBearer";
|
|
269
|
+
/**
|
|
270
|
+
* #210: the ref the broker's Lettermint mail key is held under. Unlike every
|
|
271
|
+
* other `_REF` in this file, nothing here ever delivers a value under it --
|
|
272
|
+
* {@link PlatformAuthDeps.heldKeys} only reads it back, once a human (or
|
|
273
|
+
* Archon's own onboarding) puts it there directly with the key Lettermint
|
|
274
|
+
* issued, on one allowed to send from every app's domain
|
|
275
|
+
* (`wtfalch/auth/docs/deploy.md`).
|
|
276
|
+
*/
|
|
277
|
+
export declare const PLATFORM_AUTH_BROKER_MAIL_KEY_REF = "lettermint:platform:apiToken";
|
|
237
278
|
/**
|
|
238
279
|
* The fixed description Stalwart's admin API key is minted under
|
|
239
280
|
* (`StalwartClient.createApiKey`'s own signature returns only the secret,
|
|
@@ -591,13 +632,14 @@ export declare function createAiManagementKeyMinter(options: {
|
|
|
591
632
|
}): AiManagementKeyMinter;
|
|
592
633
|
/**
|
|
593
634
|
* #211: names of the envs the platform's sign-in broker (`wtfalch/auth`'s
|
|
594
|
-
* `broker/server.ts`) refuses to start without
|
|
595
|
-
*
|
|
596
|
-
* (#
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
635
|
+
* `broker/server.ts`) refuses to start without -- `ZITADEL_SERVICE_USER_TOKEN`/
|
|
636
|
+
* `_FILE` (#209 mints and pushes this once it lands) and `LETTERMINT_API_TOKEN`
|
|
637
|
+
* (#210: `wtfalch/auth#138` moved the broker's account mail onto
|
|
638
|
+
* `@wtfalch/mailer`'s Lettermint transport, so this is the env name the
|
|
639
|
+
* broker actually reads now, not `RESEND_API_TOKEN`). Read from the broker's
|
|
640
|
+
* own live env, not a recorded scope id: whichever of #209/#210 lands first
|
|
641
|
+
* (or a human, setting either directly on the app) is enough to clear that
|
|
642
|
+
* one input, in either order, with no coupling to this step's own execution.
|
|
601
643
|
*
|
|
602
644
|
* #211 round 3: `AUTH_APPS` is a third required input -- the broker's own
|
|
603
645
|
* `parseApps` refuses to start with no `AUTH_APPS` set or an empty array
|
|
@@ -6,7 +6,7 @@ import { redactSecrets } from '../calls-sfu-deploy-support.js';
|
|
|
6
6
|
import { ensureCloudflaredConnector } from './cloudflared-connector.js';
|
|
7
7
|
import { ACCESS_POLICY_NAME, getDatabaseStarted, isNotFoundError, isRunningOrStarting, } from './executor.js';
|
|
8
8
|
import { coolifyDomains } from './hostnames.js';
|
|
9
|
-
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_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_RUNNER_DEPLOYMENT_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, 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_ZITADEL_APPLIED_IMAGE_TAG_SCOPE_KEY, PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY, PLATFORM_BACKUP_DATABASE_SCOPE_KEYS, PLATFORM_BACKUP_SERVICE_KEYS, 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_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_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_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, parseBrokerDeployRecord, parseLoginClientRecord, platformMailMxHost, } from './platform-diff.js';
|
|
9
|
+
import { AUTH_ZITADEL_RUN_OPTIONS, JOBS_SCHEDULER_KEY, PLATFORM_AI_APPLIED_GITREF_SCOPE_KEY, PLATFORM_AI_APPLIED_HOSTNAME_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_RUNNER_DEPLOYMENT_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_SET_SCOPE_KEY, PLATFORM_AI_SIGNING_KEY_UNDELIVERED_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_BROKER_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_GIT_REF_SCOPE_KEY, PLATFORM_ARCHON_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_ARCHON_CONNECTOR_APP_SCOPE_KEY, PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY, PLATFORM_ARCHON_KEY, PLATFORM_ARCHON_MASTER_CLOUDFLARE_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_TOKEN_REF, PLATFORM_ARCHON_MASTER_COOLIFY_URL_REF, 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_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_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_INTEGRATIONS_APPLIED_GITREF_SCOPE_KEY, PLATFORM_INTEGRATIONS_APPLIED_HOSTNAME_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY, PLATFORM_INTEGRATIONS_KEY_SET_SCOPE_KEY, PLATFORM_INTEGRATIONS_KEY_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_CONNECTOR_APP_SCOPE_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, parseBrokerDeployRecord, parseLoginClientRecord, platformMailMxHost, } from './platform-diff.js';
|
|
10
10
|
import { applyCallsSfuPlatformStep } from './platform-sfu.js';
|
|
11
11
|
import { CreateInstanceError, createZitadelInstance, generateSystemApiKeypair, instanceWithoutBearerMessage, systemApiUsersEnv, } from './zitadel-system-api.js';
|
|
12
12
|
/** The key `runPlatformSteps` records the apex zone's id under (`system:kind:key`). */
|
|
@@ -302,7 +302,8 @@ export class FoundryPlatformStepExecutor {
|
|
|
302
302
|
if (!this.auth) {
|
|
303
303
|
throw new Error('platform:auth needs FoundryPlatformStepExecutor to be constructed with its auth dependencies (#168)');
|
|
304
304
|
}
|
|
305
|
-
|
|
305
|
+
const company = await this.declaredOrgStore.get(context.platform.ownerOrgId);
|
|
306
|
+
return provisionPlatformAuth(context, this.auth, company?.owners ?? []);
|
|
306
307
|
}
|
|
307
308
|
if (action.system === 'coolify' && action.kind === 'app' && action.key === PLATFORM_FILES_KEY) {
|
|
308
309
|
if (!this.files) {
|
|
@@ -577,6 +578,15 @@ const PLATFORM_AUTH_ORG_NAME = 'foundry';
|
|
|
577
578
|
*/
|
|
578
579
|
export const PLATFORM_AUTH_INSTANCE_BEARER_REF = 'zitadel:platform:instanceBearer';
|
|
579
580
|
const PLATFORM_AUTH_BEARER_USER_NAME = 'foundry-instance-owner';
|
|
581
|
+
/**
|
|
582
|
+
* #210: the ref the broker's Lettermint mail key is held under. Unlike every
|
|
583
|
+
* other `_REF` in this file, nothing here ever delivers a value under it --
|
|
584
|
+
* {@link PlatformAuthDeps.heldKeys} only reads it back, once a human (or
|
|
585
|
+
* Archon's own onboarding) puts it there directly with the key Lettermint
|
|
586
|
+
* issued, on one allowed to send from every app's domain
|
|
587
|
+
* (`wtfalch/auth/docs/deploy.md`).
|
|
588
|
+
*/
|
|
589
|
+
export const PLATFORM_AUTH_BROKER_MAIL_KEY_REF = 'lettermint:platform:apiToken';
|
|
580
590
|
/**
|
|
581
591
|
* Retries `action` up to `attempts` times, sleeping increasingly between
|
|
582
592
|
* tries -- the same shape the admin machine user's own delivery retry (just
|
|
@@ -811,10 +821,15 @@ async function liveLoginClientTokenHash(coolify, appId) {
|
|
|
811
821
|
* `IAM_LOGIN_CLIENT` machine user (never the admin one, which is
|
|
812
822
|
* `IAM_OWNER`) and delivers its token to both apps, once the broker's
|
|
813
823
|
* other required inputs are already met.
|
|
814
|
-
* - The broker never receives `RESEND_API_TOKEN` either, so it will not
|
|
815
|
-
* on `AUTH_APPS` alone
|
|
816
|
-
*
|
|
817
|
-
*
|
|
824
|
+
* - ~~The broker never receives `RESEND_API_TOKEN` either, so it will not
|
|
825
|
+
* start on `AUTH_APPS` alone.~~ Closed by #210, once `wtfalch/auth#138`
|
|
826
|
+
* moved the broker's account mail onto `@wtfalch/mailer`'s Lettermint
|
|
827
|
+
* transport: this function now reads the key back from
|
|
828
|
+
* {@link PlatformAuthDeps.heldKeys} under
|
|
829
|
+
* {@link PLATFORM_AUTH_BROKER_MAIL_KEY_REF} and pushes it as
|
|
830
|
+
* `LETTERMINT_API_TOKEN`, once a human (or Archon's own onboarding) has
|
|
831
|
+
* put one there. Still a gap until then, the same "blocked, not failing"
|
|
832
|
+
* way an unset `AUTH_APPS` already is.
|
|
818
833
|
* - `AUTH_APPS` starts unset: the broker has nothing to serve until a
|
|
819
834
|
* platform app registers with it ({@link provisionPlatformArchon} is the
|
|
820
835
|
* first), so it will not start (`parseApps` throws on an empty list) until
|
|
@@ -842,7 +857,7 @@ async function liveLoginClientTokenHash(coolify, appId) {
|
|
|
842
857
|
* #197: a `cloudflared` connector for this tunnel is run
|
|
843
858
|
* ({@link ensureCloudflaredConnector}), so `hostname` answers.
|
|
844
859
|
*/
|
|
845
|
-
async function provisionPlatformAuth(context, deps) {
|
|
860
|
+
async function provisionPlatformAuth(context, deps, owners = []) {
|
|
846
861
|
const { platform } = context;
|
|
847
862
|
const hostname = platform.auth.hostname;
|
|
848
863
|
const gitRef = platform.auth.gitRef;
|
|
@@ -1043,14 +1058,24 @@ async function provisionPlatformAuth(context, deps) {
|
|
|
1043
1058
|
{ key: 'OTEL_NODE_RESOURCE_DETECTORS', value: 'none' },
|
|
1044
1059
|
{ key: 'API_CACHE_CONFIG', value: '{"defaultMinutes":2,"longMinutes":5}' },
|
|
1045
1060
|
];
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1061
|
+
// #210: read back now, not inside the deploy-decision block below -- this
|
|
1062
|
+
// push has to land before `missingBrokerInputs` is read a few lines down,
|
|
1063
|
+
// the same way `envsDiffer`/`bulkUpsertEnvs` below already do for every
|
|
1064
|
+
// other broker env, so a single apply that finds the key already held can
|
|
1065
|
+
// also see `LETTERMINT_API_TOKEN` as met and deploy in that same run.
|
|
1066
|
+
const heldMailKey = await deps.heldKeys?.resolve(PLATFORM_AUTH_BROKER_MAIL_KEY_REF);
|
|
1067
|
+
// `AUTH_APPS` and `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` are not pushed here
|
|
1068
|
+
// -- this function's own doc comment, "Known gaps". `LETTERMINT_API_TOKEN`
|
|
1069
|
+
// is, once held (#210): pushing it costs nothing even while the broker
|
|
1070
|
+
// still cannot start on the other two, and the deploy decision below stays
|
|
1071
|
+
// gated on `missingBrokerInputs` regardless.
|
|
1048
1072
|
const brokerEnvs = [
|
|
1049
1073
|
{
|
|
1050
1074
|
key: 'ZITADEL_API_URL',
|
|
1051
1075
|
value: `http://${PLATFORM_AUTH_ZITADEL_INTERNAL_NAME}:${AUTH_ZITADEL_PORT}`,
|
|
1052
1076
|
},
|
|
1053
1077
|
{ key: 'ZITADEL_DOMAIN', value: hostname },
|
|
1078
|
+
...(heldMailKey ? [{ key: 'LETTERMINT_API_TOKEN', value: heldMailKey }] : []),
|
|
1054
1079
|
];
|
|
1055
1080
|
// Compared before the write: an env change only reaches a running
|
|
1056
1081
|
// container on its next deploy (`diff.ts`'s `coolifyCalls` follows the
|
|
@@ -1074,11 +1099,11 @@ async function provisionPlatformAuth(context, deps) {
|
|
|
1074
1099
|
await deps.coolify.deploy(loginAppId);
|
|
1075
1100
|
}
|
|
1076
1101
|
// #211: the broker's own `server.ts` refuses to start without
|
|
1077
|
-
// `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` and `
|
|
1078
|
-
// it without them only produces a container that crash-loops
|
|
1079
|
-
// `/api`. The app is still created and recorded above either way,
|
|
1080
|
-
// and #210 (or a human, directly) have something to push the env
|
|
1081
|
-
// only the deploy waits.
|
|
1102
|
+
// `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` and `LETTERMINT_API_TOKEN` --
|
|
1103
|
+
// deploying it without them only produces a container that crash-loops
|
|
1104
|
+
// behind `/api`. The app is still created and recorded above either way,
|
|
1105
|
+
// so #209 and #210 (or a human, directly) have something to push the env
|
|
1106
|
+
// onto; only the deploy waits.
|
|
1082
1107
|
// #211 round 5: `brokerDeployNeeded` is its own reason to deploy (not only
|
|
1083
1108
|
// alongside justCreated/running/envsChanged, none of which this function's
|
|
1084
1109
|
// own `brokerEnvs` write above ever touches `AUTH_APPS`): it is what
|
|
@@ -1262,7 +1287,7 @@ async function provisionPlatformAuth(context, deps) {
|
|
|
1262
1287
|
// envs, then redeploys both.
|
|
1263
1288
|
//
|
|
1264
1289
|
// Deferred, not blocked, while the broker's other required inputs
|
|
1265
|
-
// (`
|
|
1290
|
+
// (`LETTERMINT_API_TOKEN`, `AUTH_APPS`) are still missing (composing with
|
|
1266
1291
|
// #211's own gating): minting and delivering here would only redeploy a
|
|
1267
1292
|
// broker that still refuses to start on the other missing input, spending
|
|
1268
1293
|
// a mint and two deploys on nothing this apply can fix. `blockedSteps`'s
|
|
@@ -1379,8 +1404,62 @@ async function provisionPlatformAuth(context, deps) {
|
|
|
1379
1404
|
// own doc comment, and `authLoginClientDrifted`'s, for why.
|
|
1380
1405
|
await record(PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, loginClientRecordValue(loginClientUserId, loginClientPatId, loginClientToken));
|
|
1381
1406
|
}
|
|
1407
|
+
// ---- ZITADEL mail and the owner's invite ----------------------------------
|
|
1408
|
+
// The token is the held key, else the broker's live env. Absent, nothing
|
|
1409
|
+
// here can run; `authBrokerBlockedReason` already reports it by name.
|
|
1410
|
+
const mailToken = heldMailKey || (await currentEnvValue(deps.coolify, brokerAppId, 'LETTERMINT_API_TOKEN'));
|
|
1411
|
+
if (mailToken) {
|
|
1412
|
+
const mailZitadel = deps.zitadelForInstance?.(`https://${hostname}`) ?? deps.zitadel;
|
|
1413
|
+
try {
|
|
1414
|
+
// Runs every time the step does (idempotent), so a rotated token lands.
|
|
1415
|
+
const smtp = await mailZitadel.ensureSmtpProvider({
|
|
1416
|
+
host: PLATFORM_AUTH_SMTP_HOST,
|
|
1417
|
+
user: PLATFORM_AUTH_SMTP_USER,
|
|
1418
|
+
password: mailToken,
|
|
1419
|
+
senderAddress: `noreply@${platform.apex}`,
|
|
1420
|
+
senderName: PLATFORM_AUTH_MAIL_SENDER_NAME,
|
|
1421
|
+
tls: true,
|
|
1422
|
+
description: PLATFORM_AUTH_SMTP_DESCRIPTION,
|
|
1423
|
+
});
|
|
1424
|
+
await record(PLATFORM_AUTH_SMTP_SCOPE_KEY, smtp.id);
|
|
1425
|
+
}
|
|
1426
|
+
catch {
|
|
1427
|
+
throw new Error(`platform:auth: could not configure ZITADEL's SMTP provider for ${hostname}; the cause is left out because it can carry the Lettermint key`);
|
|
1428
|
+
}
|
|
1429
|
+
if (!context.recordedIds()[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY]) {
|
|
1430
|
+
const owner = owners[0];
|
|
1431
|
+
if (!owner) {
|
|
1432
|
+
// No owner declared: nothing to invite. Recorded so the step is not replanned for it.
|
|
1433
|
+
await record(PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, 'none');
|
|
1434
|
+
}
|
|
1435
|
+
else {
|
|
1436
|
+
const ownerOrgId = context.recordedIds()[PLATFORM_ZITADEL_ORG_SCOPE_KEY] ??
|
|
1437
|
+
(await mailZitadel.findOrCreateOrg(PLATFORM_AUTH_ORG_NAME)).org.id;
|
|
1438
|
+
await record(PLATFORM_ZITADEL_ORG_SCOPE_KEY, ownerOrgId);
|
|
1439
|
+
let invitedUserId;
|
|
1440
|
+
try {
|
|
1441
|
+
({ userId: invitedUserId } = await mailZitadel.inviteOrgOwner(ownerOrgId, {
|
|
1442
|
+
givenName: owner.givenName,
|
|
1443
|
+
familyName: owner.familyName,
|
|
1444
|
+
email: owner.email,
|
|
1445
|
+
}));
|
|
1446
|
+
}
|
|
1447
|
+
catch {
|
|
1448
|
+
throw new Error(`platform:auth: could not invite the platform owner into ZITADEL org ${ownerOrgId}; the cause is left out because it can carry the owner's email`);
|
|
1449
|
+
}
|
|
1450
|
+
await record(PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, invitedUserId);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1382
1454
|
return { resourceId: zitadelAppId };
|
|
1383
1455
|
}
|
|
1456
|
+
/** Lettermint's SMTP relay, as ZITADEL's SMTP provider wants it: `host:port`, STARTTLS. */
|
|
1457
|
+
const PLATFORM_AUTH_SMTP_HOST = 'smtp.lettermint.co:587';
|
|
1458
|
+
const PLATFORM_AUTH_SMTP_USER = 'lettermint';
|
|
1459
|
+
/** Marks the provider as foundry's, so `ensureSmtpProvider` finds it again. */
|
|
1460
|
+
const PLATFORM_AUTH_SMTP_DESCRIPTION = 'foundry:platform:lettermint';
|
|
1461
|
+
/** The same name the broker's `from` uses (`Company Included <noreply@apex>`). */
|
|
1462
|
+
const PLATFORM_AUTH_MAIL_SENDER_NAME = 'Company Included';
|
|
1384
1463
|
// ---------------------------------------------------------------------------
|
|
1385
1464
|
// #169: the platform's own mail server (Stalwart)
|
|
1386
1465
|
// ---------------------------------------------------------------------------
|
|
@@ -1967,12 +2046,12 @@ async function mailBlockedReason(platform, mail, recordedIds) {
|
|
|
1967
2046
|
}
|
|
1968
2047
|
/**
|
|
1969
2048
|
* #211: the broker cannot start without `ZITADEL_SERVICE_USER_TOKEN`/`_FILE`,
|
|
1970
|
-
* `
|
|
1971
|
-
* deploying the broker while any is missing
|
|
1972
|
-
* this is the same check surfaced through
|
|
1973
|
-
* why, by name, rather than the broker simply
|
|
1974
|
-
* explanation. `null` while there is no broker
|
|
1975
|
-
* to check) or every input is present.
|
|
2049
|
+
* `LETTERMINT_API_TOKEN` and `AUTH_APPS`. `provisionPlatformAuth` already
|
|
2050
|
+
* skips deploying the broker while any is missing
|
|
2051
|
+
* ({@link missingBrokerInputs}); this is the same check surfaced through
|
|
2052
|
+
* `blockedSteps`, so a plan says why, by name, rather than the broker simply
|
|
2053
|
+
* sitting undeployed with no explanation. `null` while there is no broker
|
|
2054
|
+
* app id recorded yet (nothing to check) or every input is present.
|
|
1976
2055
|
*
|
|
1977
2056
|
* #211 round 3: wraps the live read the way {@link mailBlockedReason} wraps
|
|
1978
2057
|
* its own Coolify call -- a transient `listEnvs` failure must report a
|
|
@@ -1986,6 +2065,21 @@ async function mailBlockedReason(platform, mail, recordedIds) {
|
|
|
1986
2065
|
* go fix an entry, not merely wait for one of #209/#210/Archon's own
|
|
1987
2066
|
* registration to supply one), and reporting it identically hid which one
|
|
1988
2067
|
* was true.
|
|
2068
|
+
*
|
|
2069
|
+
* #210: `LETTERMINT_API_TOKEN` joins `ZITADEL_SERVICE_USER_TOKEN` in being
|
|
2070
|
+
* excluded from `otherInputsMissing` -- but only while `auth.heldKeys`
|
|
2071
|
+
* actually resolves {@link PLATFORM_AUTH_BROKER_MAIL_KEY_REF} right now.
|
|
2072
|
+
* `planAuth`'s own "loop fix" stops planning this step the moment every
|
|
2073
|
+
* `otherInputsMissing` entry looks like something only another run can
|
|
2074
|
+
* supply; excluding `LETTERMINT_API_TOKEN` unconditionally (the way
|
|
2075
|
+
* `ZITADEL_SERVICE_USER_TOKEN` is, since foundry can always eventually mint
|
|
2076
|
+
* that one) would keep the step planned, and `provisionPlatformAuth`
|
|
2077
|
+
* running its full body, forever on a host that never wires `heldKeys` or
|
|
2078
|
+
* whose operator has not put a Lettermint key there yet -- exactly the
|
|
2079
|
+
* wasteful-replan bug #211 round 5 fixed for the broker deploy itself. Once
|
|
2080
|
+
* the key *is* held, excluding it lets this run's `provisionPlatformAuth`
|
|
2081
|
+
* push it (see that function's own `heldMailKey` read) and clears the gap
|
|
2082
|
+
* for good.
|
|
1989
2083
|
*/
|
|
1990
2084
|
async function authBrokerBlockedReason(auth, recordedIds) {
|
|
1991
2085
|
const brokerAppId = recordedIds[PLATFORM_AUTH_BROKER_SCOPE_KEY];
|
|
@@ -1993,9 +2087,11 @@ async function authBrokerBlockedReason(auth, recordedIds) {
|
|
|
1993
2087
|
return { reason: null, otherInputsMissing: [] };
|
|
1994
2088
|
let missing;
|
|
1995
2089
|
let authAppsRaw;
|
|
2090
|
+
let heldMailKey;
|
|
1996
2091
|
try {
|
|
1997
2092
|
missing = await missingBrokerInputs(auth.coolify, brokerAppId);
|
|
1998
2093
|
authAppsRaw = await currentEnvValue(auth.coolify, brokerAppId, 'AUTH_APPS');
|
|
2094
|
+
heldMailKey = (await auth.heldKeys?.resolve(PLATFORM_AUTH_BROKER_MAIL_KEY_REF)) ?? null;
|
|
1999
2095
|
}
|
|
2000
2096
|
catch {
|
|
2001
2097
|
return {
|
|
@@ -2007,13 +2103,19 @@ async function authBrokerBlockedReason(auth, recordedIds) {
|
|
|
2007
2103
|
}
|
|
2008
2104
|
// #216 round 5: what `provisionPlatformAuth`'s own `loginClientOtherInputsMissing`
|
|
2009
2105
|
// gate is -- see `planAuth`'s own doc comment for why it needs this too.
|
|
2010
|
-
|
|
2106
|
+
// #210: and `LETTERMINT_API_TOKEN`, once held -- see this function's own
|
|
2107
|
+
// doc comment.
|
|
2108
|
+
const otherInputsMissing = missing.filter((m) => m !== 'ZITADEL_SERVICE_USER_TOKEN' && !(m === 'LETTERMINT_API_TOKEN' && heldMailKey));
|
|
2011
2109
|
if (missing.length === 0)
|
|
2012
2110
|
return { reason: null, otherInputsMissing };
|
|
2111
|
+
// #210: say where the operator seals the Lettermint key, not only its env name.
|
|
2112
|
+
const named = (names) => names.map((m) => m === 'LETTERMINT_API_TOKEN'
|
|
2113
|
+
? `LETTERMINT_API_TOKEN (seal it under the held-key ref ${PLATFORM_AUTH_BROKER_MAIL_KEY_REF})`
|
|
2114
|
+
: m);
|
|
2013
2115
|
if (authAppsProblem(authAppsRaw, brokerAppId) === 'malformed') {
|
|
2014
2116
|
const otherMissing = missing.filter((m) => m !== 'AUTH_APPS');
|
|
2015
2117
|
const otherSuffix = otherMissing.length > 0
|
|
2016
|
-
? `, and it has no ${otherMissing.join(' and no ')} yet (#209, #210)`
|
|
2118
|
+
? `, and it has no ${named(otherMissing).join(' and no ')} yet (#209, #210)`
|
|
2017
2119
|
: '';
|
|
2018
2120
|
return {
|
|
2019
2121
|
reason: `the platform's sign-in broker ${brokerAppId} is blocked, not deployed: its AUTH_APPS is malformed${otherSuffix}`,
|
|
@@ -2024,7 +2126,7 @@ async function authBrokerBlockedReason(auth, recordedIds) {
|
|
|
2024
2126
|
// those issues when one of them is actually among what's missing.
|
|
2025
2127
|
const suffix = missing.some((m) => m !== 'AUTH_APPS') ? ' (#209, #210)' : '';
|
|
2026
2128
|
return {
|
|
2027
|
-
reason: `the platform's sign-in broker is created but not deployed, blocked: it has no ${missing.join(' and no ')} yet${suffix}`,
|
|
2129
|
+
reason: `the platform's sign-in broker is created but not deployed, blocked: it has no ${named(missing).join(' and no ')} yet${suffix}`,
|
|
2028
2130
|
otherInputsMissing,
|
|
2029
2131
|
};
|
|
2030
2132
|
}
|
|
@@ -2197,8 +2299,8 @@ export const PLATFORM_ARCHON_CLIENT_ID_SCOPE_KEY = 'zitadel_platform_archon_clie
|
|
|
2197
2299
|
* `provisionPlatformAuth` guarantees by the time this step is reachable (its
|
|
2198
2300
|
* own resourceId records only once the broker app exists too). The broker
|
|
2199
2301
|
* will not actually be able to serve a request with this key until
|
|
2200
|
-
* `provisionPlatformAuth`'s own
|
|
2201
|
-
* `
|
|
2302
|
+
* `provisionPlatformAuth`'s own `ZITADEL_SERVICE_USER_TOKEN` is minted and
|
|
2303
|
+
* `LETTERMINT_API_TOKEN` is held; wiring the key here is still correct and
|
|
2202
2304
|
* needed so the two apps agree the moment the broker can run.
|
|
2203
2305
|
*
|
|
2204
2306
|
* Known gaps, left for review:
|
|
@@ -2486,8 +2588,8 @@ async function provisionPlatformArchon(context, deps) {
|
|
|
2486
2588
|
await deps.coolify.bulkUpsertEnvs(brokerAppId, desiredAuthApps);
|
|
2487
2589
|
}
|
|
2488
2590
|
// #211 round 2: deploying the broker before its own `server.ts` can start
|
|
2489
|
-
// (missing `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` or `
|
|
2490
|
-
// #210) only produces a container that crash-loops behind `/api` -- the
|
|
2591
|
+
// (missing `ZITADEL_SERVICE_USER_TOKEN`/`_FILE` or `LETTERMINT_API_TOKEN`,
|
|
2592
|
+
// #209, #210) only produces a container that crash-loops behind `/api` -- the
|
|
2491
2593
|
// same rule `provisionPlatformAuth` follows for its own deploy of this
|
|
2492
2594
|
// app. The env is still written either way, so the broker serves the new
|
|
2493
2595
|
// registration the moment it is next deployed, by either step.
|
|
@@ -3987,13 +4089,14 @@ async function findOrCreatePlatformApp(coolify, name, environment, create) {
|
|
|
3987
4089
|
}
|
|
3988
4090
|
/**
|
|
3989
4091
|
* #211: names of the envs the platform's sign-in broker (`wtfalch/auth`'s
|
|
3990
|
-
* `broker/server.ts`) refuses to start without
|
|
3991
|
-
*
|
|
3992
|
-
* (#
|
|
3993
|
-
*
|
|
3994
|
-
*
|
|
3995
|
-
*
|
|
3996
|
-
*
|
|
4092
|
+
* `broker/server.ts`) refuses to start without -- `ZITADEL_SERVICE_USER_TOKEN`/
|
|
4093
|
+
* `_FILE` (#209 mints and pushes this once it lands) and `LETTERMINT_API_TOKEN`
|
|
4094
|
+
* (#210: `wtfalch/auth#138` moved the broker's account mail onto
|
|
4095
|
+
* `@wtfalch/mailer`'s Lettermint transport, so this is the env name the
|
|
4096
|
+
* broker actually reads now, not `RESEND_API_TOKEN`). Read from the broker's
|
|
4097
|
+
* own live env, not a recorded scope id: whichever of #209/#210 lands first
|
|
4098
|
+
* (or a human, setting either directly on the app) is enough to clear that
|
|
4099
|
+
* one input, in either order, with no coupling to this step's own execution.
|
|
3997
4100
|
*
|
|
3998
4101
|
* #211 round 3: `AUTH_APPS` is a third required input -- the broker's own
|
|
3999
4102
|
* `parseApps` refuses to start with no `AUTH_APPS` set or an empty array
|
|
@@ -4011,8 +4114,8 @@ export async function missingBrokerInputs(coolify, brokerAppId) {
|
|
|
4011
4114
|
if (!has('ZITADEL_SERVICE_USER_TOKEN') && !has('ZITADEL_SERVICE_USER_TOKEN_FILE')) {
|
|
4012
4115
|
missing.push('ZITADEL_SERVICE_USER_TOKEN');
|
|
4013
4116
|
}
|
|
4014
|
-
if (!has('
|
|
4015
|
-
missing.push('
|
|
4117
|
+
if (!has('LETTERMINT_API_TOKEN'))
|
|
4118
|
+
missing.push('LETTERMINT_API_TOKEN');
|
|
4016
4119
|
if (authAppsProblem(value('AUTH_APPS'), brokerAppId) !== null)
|
|
4017
4120
|
missing.push('AUTH_APPS');
|
|
4018
4121
|
return missing;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.14",
|
|
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",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@wtfalch/audit": "0.6.0",
|
|
44
44
|
"@wtfalch/auth": "0.16.0",
|
|
45
45
|
"@wtfalch/email": "^0.17.0",
|
|
46
|
-
"@wtfalch/providers": "0.
|
|
46
|
+
"@wtfalch/providers": "0.7.2",
|
|
47
47
|
"ssh2": "1.17.0",
|
|
48
48
|
"zod": "4.6.5"
|
|
49
49
|
},
|