@wtfalch/foundry 0.16.13 → 0.16.15
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.
|
@@ -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. */
|
|
@@ -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
|
|
@@ -198,7 +217,7 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
198
217
|
private readonly ai?;
|
|
199
218
|
private readonly archon?;
|
|
200
219
|
private readonly backup?;
|
|
201
|
-
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);
|
|
202
221
|
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
203
222
|
auth?: string;
|
|
204
223
|
authExecutorMissing?: boolean;
|
|
@@ -240,6 +259,14 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
240
259
|
private executeSfu;
|
|
241
260
|
private coolifyJobs;
|
|
242
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Every ZITADEL OIDC/management/console/asset path -- `deploy/proxy/Caddyfile`'s
|
|
264
|
+
* own explicit list, restated here as one regex since the tunnel routes by a
|
|
265
|
+
* single `path` per ingress rule rather than a set. Anything not matching
|
|
266
|
+
* falls through to the login app's own ingress rule below (login v2 answers
|
|
267
|
+
* everything else, including the root).
|
|
268
|
+
*/
|
|
269
|
+
export declare const AUTH_ZITADEL_PATH_PATTERN = "^/(oidc|oauth|saml|management|admin|system|auth|assets|debug/healthz|ui/console|\\.well-known|v2|v2beta|zitadel\\.[A-Za-z0-9.]+)(/|$)";
|
|
243
270
|
/**
|
|
244
271
|
* #283: the ref the new instance's IAM_OWNER machine token (a PAT) is
|
|
245
272
|
* delivered under, once. A host reads it back to build `zitadelForInstance`
|
|
@@ -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) {
|
|
@@ -549,7 +550,11 @@ const AUTH_BROKER_PORT = '3000';
|
|
|
549
550
|
* falls through to the login app's own ingress rule below (login v2 answers
|
|
550
551
|
* everything else, including the root).
|
|
551
552
|
*/
|
|
552
|
-
|
|
553
|
+
// `v2`, `v2beta` and `zitadel.<service>` are ZITADEL's v2 REST and connect
|
|
554
|
+
// APIs (providers' `inviteOrgOwner` calls `POST /v2/users`); without them those
|
|
555
|
+
// calls reach the login app and answer 404. The login app itself lives under
|
|
556
|
+
// `/ui/v2/login`, which none of these match.
|
|
557
|
+
export const AUTH_ZITADEL_PATH_PATTERN = '^/(oidc|oauth|saml|management|admin|system|auth|assets|debug/healthz|ui/console|\\.well-known|v2|v2beta|zitadel\\.[A-Za-z0-9.]+)(/|$)';
|
|
553
558
|
/**
|
|
554
559
|
* `@wtfalch/auth`'s own `brokerUrl` default, `<issuer>/api`
|
|
555
560
|
* (`packages/auth/src/config.ts`) -- `deploy/proxy/Caddyfile`'s own
|
|
@@ -856,7 +861,7 @@ async function liveLoginClientTokenHash(coolify, appId) {
|
|
|
856
861
|
* #197: a `cloudflared` connector for this tunnel is run
|
|
857
862
|
* ({@link ensureCloudflaredConnector}), so `hostname` answers.
|
|
858
863
|
*/
|
|
859
|
-
async function provisionPlatformAuth(context, deps) {
|
|
864
|
+
async function provisionPlatformAuth(context, deps, owners = []) {
|
|
860
865
|
const { platform } = context;
|
|
861
866
|
const hostname = platform.auth.hostname;
|
|
862
867
|
const gitRef = platform.auth.gitRef;
|
|
@@ -1403,8 +1408,72 @@ async function provisionPlatformAuth(context, deps) {
|
|
|
1403
1408
|
// own doc comment, and `authLoginClientDrifted`'s, for why.
|
|
1404
1409
|
await record(PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY, loginClientRecordValue(loginClientUserId, loginClientPatId, loginClientToken));
|
|
1405
1410
|
}
|
|
1411
|
+
// ---- ZITADEL mail and the owner's invite ----------------------------------
|
|
1412
|
+
// The token is the held key, else the broker's live env. Absent, nothing
|
|
1413
|
+
// here can run; `authBrokerBlockedReason` already reports it by name.
|
|
1414
|
+
const mailToken = heldMailKey || (await currentEnvValue(deps.coolify, brokerAppId, 'LETTERMINT_API_TOKEN'));
|
|
1415
|
+
if (mailToken) {
|
|
1416
|
+
const mailZitadel = deps.zitadelForInstance?.(`https://${hostname}`) ?? deps.zitadel;
|
|
1417
|
+
try {
|
|
1418
|
+
// Runs every time the step does (idempotent), so a rotated token lands.
|
|
1419
|
+
const smtp = await mailZitadel.ensureSmtpProvider({
|
|
1420
|
+
host: PLATFORM_AUTH_SMTP_HOST,
|
|
1421
|
+
user: PLATFORM_AUTH_SMTP_USER,
|
|
1422
|
+
password: mailToken,
|
|
1423
|
+
senderAddress: `noreply@${platform.apex}`,
|
|
1424
|
+
senderName: PLATFORM_AUTH_MAIL_SENDER_NAME,
|
|
1425
|
+
tls: true,
|
|
1426
|
+
description: PLATFORM_AUTH_SMTP_DESCRIPTION,
|
|
1427
|
+
});
|
|
1428
|
+
await record(PLATFORM_AUTH_SMTP_SCOPE_KEY, smtp.id);
|
|
1429
|
+
}
|
|
1430
|
+
catch (error) {
|
|
1431
|
+
// The cause, with the Lettermint key redacted, so a failure says why.
|
|
1432
|
+
const cause = redactSecrets(error instanceof Error ? error.message : String(error), [
|
|
1433
|
+
mailToken,
|
|
1434
|
+
]);
|
|
1435
|
+
throw new Error(`platform:auth: could not configure ZITADEL's SMTP provider for ${hostname}: ${cause}`);
|
|
1436
|
+
}
|
|
1437
|
+
if (!context.recordedIds()[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY]) {
|
|
1438
|
+
const owner = owners[0];
|
|
1439
|
+
if (!owner) {
|
|
1440
|
+
// No owner declared: nothing to invite. Recorded so the step is not replanned for it.
|
|
1441
|
+
await record(PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, 'none');
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
const ownerOrgId = context.recordedIds()[PLATFORM_ZITADEL_ORG_SCOPE_KEY] ??
|
|
1445
|
+
(await mailZitadel.findOrCreateOrg(PLATFORM_AUTH_ORG_NAME)).org.id;
|
|
1446
|
+
await record(PLATFORM_ZITADEL_ORG_SCOPE_KEY, ownerOrgId);
|
|
1447
|
+
let invitedUserId;
|
|
1448
|
+
try {
|
|
1449
|
+
({ userId: invitedUserId } = await mailZitadel.inviteOrgOwner(ownerOrgId, {
|
|
1450
|
+
givenName: owner.givenName,
|
|
1451
|
+
familyName: owner.familyName,
|
|
1452
|
+
email: owner.email,
|
|
1453
|
+
}));
|
|
1454
|
+
}
|
|
1455
|
+
catch (error) {
|
|
1456
|
+
// The cause, with the owner's email and the Lettermint key redacted, so a failure says why.
|
|
1457
|
+
const cause = redactSecrets(error instanceof Error ? error.message : String(error), [
|
|
1458
|
+
owner.email,
|
|
1459
|
+
owner.email.toLowerCase(),
|
|
1460
|
+
mailToken,
|
|
1461
|
+
]);
|
|
1462
|
+
throw new Error(`platform:auth: could not invite the platform owner into ZITADEL org ${ownerOrgId}: ${cause}`);
|
|
1463
|
+
}
|
|
1464
|
+
await record(PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY, invitedUserId);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1406
1468
|
return { resourceId: zitadelAppId };
|
|
1407
1469
|
}
|
|
1470
|
+
/** Lettermint's SMTP relay, as ZITADEL's SMTP provider wants it: `host:port`, STARTTLS. */
|
|
1471
|
+
const PLATFORM_AUTH_SMTP_HOST = 'smtp.lettermint.co:587';
|
|
1472
|
+
const PLATFORM_AUTH_SMTP_USER = 'lettermint';
|
|
1473
|
+
/** Marks the provider as foundry's, so `ensureSmtpProvider` finds it again. */
|
|
1474
|
+
const PLATFORM_AUTH_SMTP_DESCRIPTION = 'foundry:platform:lettermint';
|
|
1475
|
+
/** The same name the broker's `from` uses (`Company Included <noreply@apex>`). */
|
|
1476
|
+
const PLATFORM_AUTH_MAIL_SENDER_NAME = 'Company Included';
|
|
1408
1477
|
// ---------------------------------------------------------------------------
|
|
1409
1478
|
// #169: the platform's own mail server (Stalwart)
|
|
1410
1479
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.15",
|
|
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
|
},
|