@wtfalch/foundry 0.35.1 → 0.36.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/plan/diff.js +2 -4
- package/dist/plan/executor.d.ts +9 -0
- package/dist/plan/platform-diff.js +7 -6
- package/dist/plan/platform-providers.d.ts +1 -1
- package/dist/plan/platform-providers.js +30 -11
- package/dist/plan/sign-in-org.d.ts +20 -9
- package/dist/plan/sign-in-org.js +42 -15
- package/package.json +3 -3
package/dist/plan/diff.js
CHANGED
|
@@ -899,7 +899,7 @@ function planOrgRegistrationClosed(org, recordedIds, authReady, issuerStep) {
|
|
|
899
899
|
},
|
|
900
900
|
};
|
|
901
901
|
}
|
|
902
|
-
/** The
|
|
902
|
+
/** The note for an org sign-in domain the sign-in server refused to make primary, while it is not registered. */
|
|
903
903
|
function orgDomainBlocked(org, recordedIds) {
|
|
904
904
|
const domain = signInOrgDomain(org);
|
|
905
905
|
if (recordedIds[ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY]?.toLowerCase() !== domain)
|
|
@@ -925,9 +925,7 @@ function planOrgDomain(org, recordedIds, authReady, issuerStep) {
|
|
|
925
925
|
const domain = signInOrgDomain(org);
|
|
926
926
|
if (recordedIds[ZITADEL_ORG_DOMAIN_SCOPE_KEY]?.toLowerCase() === domain)
|
|
927
927
|
return null;
|
|
928
|
-
//
|
|
929
|
-
if (recordedIds[ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY]?.toLowerCase() === domain)
|
|
930
|
-
return null;
|
|
928
|
+
// A refused domain is planned again (and reported, see `orgDomainBlocked`): the step sets the policy it needs.
|
|
931
929
|
return {
|
|
932
930
|
action: { system: 'zitadel', kind: 'domain', key: org.id, desired: { domain } },
|
|
933
931
|
step: {
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -936,6 +936,15 @@ export interface ZitadelPort {
|
|
|
936
936
|
addOrgDomain(orgId: string, domain: string): Promise<void>;
|
|
937
937
|
/** Makes a domain already on the org its primary one. Idempotent: no-op when it already is. */
|
|
938
938
|
setPrimaryOrgDomain(orgId: string, domain: string): Promise<void>;
|
|
939
|
+
/** Removes a domain from the org. The server refuses the primary one. */
|
|
940
|
+
removeOrgDomain(orgId: string, domain: string): Promise<void>;
|
|
941
|
+
/** Sets the instance's domain policy. Writes only when a field differs. */
|
|
942
|
+
ensureDomainPolicy(want: {
|
|
943
|
+
userLoginMustBeDomain: boolean;
|
|
944
|
+
validateOrgDomains: boolean;
|
|
945
|
+
}): Promise<{
|
|
946
|
+
changed: boolean;
|
|
947
|
+
}>;
|
|
939
948
|
setUserMetadata(orgId: string, userId: string, key: string, value: string): Promise<void>;
|
|
940
949
|
getUserMetadata(orgId: string, userId: string, key: string): Promise<string | null>;
|
|
941
950
|
findUserByEmail(orgId: string, email: string): Promise<{
|
|
@@ -9,7 +9,7 @@ import { legacyAppHostname } from './hostnames.js';
|
|
|
9
9
|
import { PLATFORM_MAIL_RELAY_KEY, planMailRelay } from './mail-relay.js';
|
|
10
10
|
import { planOikosIngress } from './oikos-ingress.js';
|
|
11
11
|
import { callsSfuAppliedFingerprint } from './platform-sfu.js';
|
|
12
|
-
import {
|
|
12
|
+
import { signInDomainBlockedText } from './sign-in-org.js';
|
|
13
13
|
import { withStepSystem } from './step-system.js';
|
|
14
14
|
/**
|
|
15
15
|
* The `Plan` (#19's public contract) a {@link PlatformPlanDetail} carries,
|
|
@@ -2905,8 +2905,8 @@ export const ARCHON_ORG_BLOCKED = 'Archon is blocked, not planned: its own sign-
|
|
|
2905
2905
|
* {@link PLATFORM_ARCHON_KEY}, planned while any of those is not recorded
|
|
2906
2906
|
* against the recorded org, and always before `platform:archon`. Blocked, not
|
|
2907
2907
|
* planned, while the host cannot reach the instance. A refused domain is
|
|
2908
|
-
* reported as a standing note (as the owner org's is) and
|
|
2909
|
-
* step; an invite that cannot be sent (no work address, no mail port) is a
|
|
2908
|
+
* reported as a standing note (as the owner org's is) and planned again, since
|
|
2909
|
+
* the step sets the domain policy it needs; an invite that cannot be sent (no work address, no mail port) is a
|
|
2910
2910
|
* standing note and does not hold it either.
|
|
2911
2911
|
*/
|
|
2912
2912
|
function planArchonOrg(platform, recordedIds, executorBlocked, inviteBlocked, ownerSignIn) {
|
|
@@ -2914,7 +2914,8 @@ function planArchonOrg(platform, recordedIds, executorBlocked, inviteBlocked, ow
|
|
|
2914
2914
|
return { step: null, blocked: null };
|
|
2915
2915
|
const orgId = recordedIds[PLATFORM_ARCHON_ORG_SCOPE_KEY];
|
|
2916
2916
|
const blockedDomain = recordedIds[PLATFORM_ARCHON_ORG_DOMAIN_BLOCKED_SCOPE_KEY];
|
|
2917
|
-
|
|
2917
|
+
// A refused domain is planned again: the step sets the domain policy it needs.
|
|
2918
|
+
const domainDone = Boolean(recordedIds[PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY]);
|
|
2918
2919
|
const inviteRecorded = recordedIds[PLATFORM_ARCHON_OWNER_INVITE_SCOPE_KEY];
|
|
2919
2920
|
const inviteDone = Boolean(inviteBlocked) ||
|
|
2920
2921
|
inviteRecorded === 'none' ||
|
|
@@ -2922,7 +2923,7 @@ function planArchonOrg(platform, recordedIds, executorBlocked, inviteBlocked, ow
|
|
|
2922
2923
|
const standing = [
|
|
2923
2924
|
inviteBlocked,
|
|
2924
2925
|
blockedDomain && !recordedIds[PLATFORM_ARCHON_ORG_DOMAIN_SCOPE_KEY]
|
|
2925
|
-
?
|
|
2926
|
+
? signInDomainBlockedText('Archon', blockedDomain)
|
|
2926
2927
|
: null,
|
|
2927
2928
|
].filter((v) => Boolean(v));
|
|
2928
2929
|
const standingNote = standing.length > 0 ? standing.join('; ') : null;
|
|
@@ -3042,5 +3043,5 @@ function ownerDomainBlockedText(recordedIds) {
|
|
|
3042
3043
|
return null;
|
|
3043
3044
|
if (recordedIds[PLATFORM_OWNER_ORG_DOMAIN_SCOPE_KEY] === blockedDomain)
|
|
3044
3045
|
return null;
|
|
3045
|
-
return signInDomainBlockedText('the platform owner', blockedDomain)
|
|
3046
|
+
return signInDomainBlockedText('the platform owner', blockedDomain);
|
|
3046
3047
|
}
|
|
@@ -112,7 +112,7 @@ export interface PlatformAuthDeps {
|
|
|
112
112
|
tunnels: PlatformTunnelsFactory;
|
|
113
113
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
114
114
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
115
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'reserveUserName' | 'closeSelfRegistration' | 'enforceSecondFactor' | 'inviteOrgOwner' | 'addOrgDomain' | 'setPrimaryOrgDomain' | 'setUserMetadata' | 'getUserMetadata'> & PlatformZitadelSmtp;
|
|
115
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'reserveUserName' | 'closeSelfRegistration' | 'enforceSecondFactor' | 'inviteOrgOwner' | 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain' | 'setUserMetadata' | 'getUserMetadata'> & PlatformZitadelSmtp;
|
|
116
116
|
/**
|
|
117
117
|
* Mails the owner's first-sign-in link to their personal address (the same
|
|
118
118
|
* port as `FoundryStepExecutorPorts.ownerInviteMail`). The link carries the
|
|
@@ -3210,14 +3210,13 @@ const PLATFORM_ARCHON_PROJECT = 'platform';
|
|
|
3210
3210
|
const PLATFORM_ARCHON_APP_NAME = 'platform-archon';
|
|
3211
3211
|
const PLATFORM_ARCHON_DB_NAME = 'platform-archon-db';
|
|
3212
3212
|
/**
|
|
3213
|
-
* The database Archon runs on.
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
*
|
|
3217
|
-
*
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
const ARCHON_POSTGRES_DB = 'archon_v2';
|
|
3213
|
+
* The database Archon runs on, and its owner. A platform made from zero
|
|
3214
|
+
* (2026-10-12) creates `platform-archon-db` with this as its first database,
|
|
3215
|
+
* so both URLs built here set it themselves and never read
|
|
3216
|
+
* `internal_db_url`'s path. Archon's own migrations grant to
|
|
3217
|
+
* `current_database() || '_rt'`, so {@link ARCHON_RUNTIME_ROLE} follows.
|
|
3218
|
+
*/
|
|
3219
|
+
const ARCHON_POSTGRES_DB = 'archon';
|
|
3221
3220
|
const ARCHON_RUNTIME_ROLE = `${ARCHON_POSTGRES_DB}_rt`;
|
|
3222
3221
|
/** Fixed so the tunnel addresses the container directly (see `provisionPlatformAuth`). */
|
|
3223
3222
|
const PLATFORM_ARCHON_INTERNAL_NAME = 'platform-archon';
|
|
@@ -3691,6 +3690,7 @@ async function provisionPlatformArchon(context, deps, ownerCompany = null) {
|
|
|
3691
3690
|
const ownerUrl = archonDatabaseUrl(db.internal_db_url);
|
|
3692
3691
|
if (liveOwnerUrl) {
|
|
3693
3692
|
assertSameDatabaseHost('DATABASE_URL_OWNER', liveOwnerUrl, db.internal_db_url);
|
|
3693
|
+
assertArchonDatabasePath('DATABASE_URL_OWNER', liveOwnerUrl);
|
|
3694
3694
|
}
|
|
3695
3695
|
else {
|
|
3696
3696
|
envs.push({ key: 'DATABASE_URL_OWNER', value: ownerUrl });
|
|
@@ -5070,12 +5070,30 @@ function assertSameDatabaseHost(key, liveUrl, dbInternalUrl) {
|
|
|
5070
5070
|
throw new Error(`platform:archon: ${key} on the app points at host ${liveHost ?? '(unparseable)'}, but the adopted database ${PLATFORM_ARCHON_DB_NAME} is at host ${dbHost ?? '(unparseable)'}; refusing to leave the app's database URLs on different databases`);
|
|
5071
5071
|
}
|
|
5072
5072
|
}
|
|
5073
|
+
/**
|
|
5074
|
+
* Throws when `liveUrl`, kept as it is on the app, names another database than
|
|
5075
|
+
* {@link ARCHON_POSTGRES_DB}: a hand-set URL on the wrong path, or a platform
|
|
5076
|
+
* still on the retired `archon_v2`. Names the two databases only, never the URL.
|
|
5077
|
+
*/
|
|
5078
|
+
function assertArchonDatabasePath(key, liveUrl) {
|
|
5079
|
+
let database;
|
|
5080
|
+
try {
|
|
5081
|
+
database = decodeURIComponent(new URL(liveUrl).pathname.slice(1));
|
|
5082
|
+
}
|
|
5083
|
+
catch {
|
|
5084
|
+
database = null;
|
|
5085
|
+
}
|
|
5086
|
+
if (database !== ARCHON_POSTGRES_DB) {
|
|
5087
|
+
throw new Error(`platform:archon: ${key} on the app names database ${database ?? '(unparseable)'}, but Archon runs on database ${ARCHON_POSTGRES_DB}; refusing to keep it. Point ${key} at ${ARCHON_POSTGRES_DB} in Coolify and apply again`);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5073
5090
|
async function ensureArchonRuntimeUrl(coolify, appId, dbInternalUrl) {
|
|
5074
5091
|
const envs = await coolify.listEnvs(appId);
|
|
5075
5092
|
const live = envs.find((e) => e.key === 'DATABASE_URL' && !e.is_preview);
|
|
5076
5093
|
if (live) {
|
|
5077
5094
|
// #265: kept as it is, but it must name the adopted database's host.
|
|
5078
5095
|
assertSameDatabaseHost('DATABASE_URL', live.value, dbInternalUrl);
|
|
5096
|
+
assertArchonDatabasePath('DATABASE_URL', live.value);
|
|
5079
5097
|
return null;
|
|
5080
5098
|
}
|
|
5081
5099
|
let host;
|
|
@@ -8130,9 +8148,10 @@ const PLATFORM_BACKUP_S3_STORAGE_PENDING_SCOPE_KEY = 'coolify_platform_backup_s3
|
|
|
8130
8148
|
const PLATFORM_BACKUP_DEFAULT_FREQUENCY = 'daily';
|
|
8131
8149
|
/**
|
|
8132
8150
|
* The database a service's schedule must name. A schedule that names none
|
|
8133
|
-
* dumps the Coolify resource's first database
|
|
8134
|
-
*
|
|
8135
|
-
* Every other service runs on its resource's first database and
|
|
8151
|
+
* dumps the Coolify resource's first database; Archon's is named here anyway,
|
|
8152
|
+
* so a resource whose first database is not {@link ARCHON_POSTGRES_DB} is still
|
|
8153
|
+
* dumped right. Every other service runs on its resource's first database and
|
|
8154
|
+
* needs no entry.
|
|
8136
8155
|
*/
|
|
8137
8156
|
const PLATFORM_BACKUP_DATABASE_NAMES = {
|
|
8138
8157
|
archon: ARCHON_POSTGRES_DB,
|
|
@@ -9,30 +9,41 @@ export declare const ZITADEL_ORG_DOMAIN_SCOPE_KEY = "zitadel_org_domain";
|
|
|
9
9
|
export declare const ZITADEL_ORG_REGISTRATION_CLOSED_SCOPE_KEY = "zitadel_org_registration_closed";
|
|
10
10
|
/** Closes self-registration on the org (idempotent) and records it under `scopeKey`, in that order. */
|
|
11
11
|
export declare function closeSignInOrgRegistration(zitadel: Pick<ZitadelPort, 'closeSelfRegistration'>, orgId: string, recordScopeId: (key: string, id: string) => void, scopeKey?: string): Promise<void>;
|
|
12
|
-
/** Scope id: the domain the sign-in server refused to make primary
|
|
12
|
+
/** Scope id: the domain the sign-in server still refused to make primary after foundry set its domain policy and added the domain again. Set instead of failing; the planner reports it and plans the domain again. */
|
|
13
13
|
export declare const ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY = "zitadel_org_domain_blocked";
|
|
14
|
-
/** The fixed text of the
|
|
14
|
+
/** The fixed text of the note for a refused sign-in domain. No cause, no value but names. */
|
|
15
15
|
export declare function signInDomainBlockedText(orgName: string, domain: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The domain policy a sign-in server needs before foundry gives an org its
|
|
18
|
+
* domain. The server holds only orgs foundry made, so a domain needs no proof
|
|
19
|
+
* of ownership, and a person's login name carries their org's domain.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SIGN_IN_DOMAIN_POLICY: {
|
|
22
|
+
userLoginMustBeDomain: boolean;
|
|
23
|
+
validateOrgDomains: boolean;
|
|
24
|
+
};
|
|
16
25
|
/** The domain an org's owners sign in on: its own, or `<slug>.oikos.host` when it has none. */
|
|
17
26
|
export declare function signInOrgDomain(org: {
|
|
18
27
|
slug: string;
|
|
19
28
|
domain: string | null;
|
|
20
29
|
}): string;
|
|
21
30
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
31
|
+
* Sets the server's domain policy ({@link SIGN_IN_DOMAIN_POLICY}), adds
|
|
32
|
+
* `domain` to the ZITADEL org and makes it primary. Every call is idempotent.
|
|
33
|
+
* A domain added while the policy still wanted proof stays unverified, so it
|
|
34
|
+
* is removed and added again. A failure after the add leaves the domain on
|
|
35
|
+
* the org but not primary, and nothing is recorded, so the next run repeats.
|
|
25
36
|
*/
|
|
26
|
-
export declare function registerSignInDomain(zitadel: Pick<ZitadelPort, 'addOrgDomain' | 'setPrimaryOrgDomain'>, orgId: string, domain: string, sleep?: (ms: number) => Promise<void>): Promise<void>;
|
|
37
|
+
export declare function registerSignInDomain(zitadel: Pick<ZitadelPort, 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain'>, orgId: string, domain: string, sleep?: (ms: number) => Promise<void>): Promise<void>;
|
|
27
38
|
/**
|
|
28
39
|
* {@link registerSignInDomain}, except that a refusal to make the domain
|
|
29
|
-
* primary (
|
|
40
|
+
* primary (still unverified after the policy and the second add) is recorded under
|
|
30
41
|
* `blockedScopeKey` and returns `false` instead of throwing on every apply.
|
|
31
42
|
* Any other failure throws. Returns `true` when the domain is primary.
|
|
32
43
|
*/
|
|
33
|
-
export declare function registerSignInDomainOrBlock(zitadel: Pick<ZitadelPort, 'addOrgDomain' | 'setPrimaryOrgDomain'>, orgId: string, domain: string, recordScopeId: (key: string, id: string) => void, blockedScopeKey?: string): Promise<boolean>;
|
|
44
|
+
export declare function registerSignInDomainOrBlock(zitadel: Pick<ZitadelPort, 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain'>, orgId: string, domain: string, recordScopeId: (key: string, id: string) => void, blockedScopeKey?: string): Promise<boolean>;
|
|
34
45
|
export interface EnsureSignInOrgOptions {
|
|
35
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'addOrgDomain' | 'setPrimaryOrgDomain' | 'closeSelfRegistration'>;
|
|
46
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain' | 'closeSelfRegistration'>;
|
|
36
47
|
/** Where the successful close is recorded (the org id). Absent: not recorded; the caller closes on every run. */
|
|
37
48
|
registrationClosedScopeKey?: string;
|
|
38
49
|
/** Defaults to {@link ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY}. */
|
package/dist/plan/sign-in-org.js
CHANGED
|
@@ -11,12 +11,18 @@ export async function closeSignInOrgRegistration(zitadel, orgId, recordScopeId,
|
|
|
11
11
|
await zitadel.closeSelfRegistration(orgId);
|
|
12
12
|
recordScopeId(scopeKey, orgId);
|
|
13
13
|
}
|
|
14
|
-
/** Scope id: the domain the sign-in server refused to make primary
|
|
14
|
+
/** Scope id: the domain the sign-in server still refused to make primary after foundry set its domain policy and added the domain again. Set instead of failing; the planner reports it and plans the domain again. */
|
|
15
15
|
export const ZITADEL_ORG_DOMAIN_BLOCKED_SCOPE_KEY = 'zitadel_org_domain_blocked';
|
|
16
|
-
/** The fixed text of the
|
|
16
|
+
/** The fixed text of the note for a refused sign-in domain. No cause, no value but names. */
|
|
17
17
|
export function signInDomainBlockedText(orgName, domain) {
|
|
18
|
-
return `${orgName}'s sign-in domain ${domain}
|
|
18
|
+
return `${orgName}'s sign-in domain ${domain} was refused: the sign-in server would not make it primary even after foundry turned off its proof of ownership (ValidateOrgDomains) and added the domain again. The next apply tries again`;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* The domain policy a sign-in server needs before foundry gives an org its
|
|
22
|
+
* domain. The server holds only orgs foundry made, so a domain needs no proof
|
|
23
|
+
* of ownership, and a person's login name carries their org's domain.
|
|
24
|
+
*/
|
|
25
|
+
export const SIGN_IN_DOMAIN_POLICY = { userLoginMustBeDomain: true, validateOrgDomains: false };
|
|
20
26
|
/** True for the providers' "domain is not verified" refusal of `setPrimaryOrgDomain`. */
|
|
21
27
|
function isPrimaryDomainRefused(error) {
|
|
22
28
|
return error instanceof Error && /is not verified/.test(error.message);
|
|
@@ -31,30 +37,51 @@ const DOMAIN_LISTED_WAIT_MS = 500;
|
|
|
31
37
|
export function signInOrgDomain(org) {
|
|
32
38
|
return (org.domain ?? `${org.slug}.oikos.host`).toLowerCase();
|
|
33
39
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
* idempotent. A failure after the add leaves the domain on the org but not
|
|
37
|
-
* primary, and nothing is recorded, so the next run repeats both.
|
|
38
|
-
*/
|
|
39
|
-
export async function registerSignInDomain(zitadel, orgId, domain, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
40
|
-
await zitadel.addOrgDomain(orgId, domain);
|
|
41
|
-
// The sign-in server lists a domain a moment after it accepts the add, and
|
|
42
|
-
// `setPrimaryOrgDomain` reads that list first: ask again, for five seconds.
|
|
40
|
+
/** Retries `work` while `retry` accepts its error, for five seconds. */
|
|
41
|
+
async function untilListed(work, retry, sleep) {
|
|
43
42
|
for (let attempt = 1;; attempt += 1) {
|
|
44
43
|
try {
|
|
45
|
-
await
|
|
44
|
+
await work();
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
47
|
catch (error) {
|
|
49
|
-
if (attempt >= DOMAIN_LISTED_TRIES || !
|
|
48
|
+
if (attempt >= DOMAIN_LISTED_TRIES || !retry(error))
|
|
50
49
|
throw error;
|
|
51
50
|
await sleep(DOMAIN_LISTED_WAIT_MS);
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Sets the server's domain policy ({@link SIGN_IN_DOMAIN_POLICY}), adds
|
|
56
|
+
* `domain` to the ZITADEL org and makes it primary. Every call is idempotent.
|
|
57
|
+
* A domain added while the policy still wanted proof stays unverified, so it
|
|
58
|
+
* is removed and added again. A failure after the add leaves the domain on
|
|
59
|
+
* the org but not primary, and nothing is recorded, so the next run repeats.
|
|
60
|
+
*/
|
|
61
|
+
export async function registerSignInDomain(zitadel, orgId, domain, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
62
|
+
await zitadel.ensureDomainPolicy(SIGN_IN_DOMAIN_POLICY);
|
|
63
|
+
await zitadel.addOrgDomain(orgId, domain);
|
|
64
|
+
// The sign-in server lists a domain a moment after it accepts the add, and
|
|
65
|
+
// `setPrimaryOrgDomain` reads that list first: ask again, for five seconds.
|
|
66
|
+
try {
|
|
67
|
+
await untilListed(() => zitadel.setPrimaryOrgDomain(orgId, domain), isDomainNotListed, sleep);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (!isPrimaryDomainRefused(error))
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
await zitadel.removeOrgDomain(orgId, domain);
|
|
75
|
+
// Until the server lists the removal, the add sees the old unverified
|
|
76
|
+
// domain and skips: both answers are a moment late, so ask again.
|
|
77
|
+
await untilListed(async () => {
|
|
78
|
+
await zitadel.addOrgDomain(orgId, domain);
|
|
79
|
+
await zitadel.setPrimaryOrgDomain(orgId, domain);
|
|
80
|
+
}, (error) => isDomainNotListed(error) || isPrimaryDomainRefused(error), sleep);
|
|
81
|
+
}
|
|
55
82
|
/**
|
|
56
83
|
* {@link registerSignInDomain}, except that a refusal to make the domain
|
|
57
|
-
* primary (
|
|
84
|
+
* primary (still unverified after the policy and the second add) is recorded under
|
|
58
85
|
* `blockedScopeKey` and returns `false` instead of throwing on every apply.
|
|
59
86
|
* Any other failure throws. Returns `true` when the domain is primary.
|
|
60
87
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/foundry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"status": "node scripts/status.mjs"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@wtfalch/providers": ">=0.
|
|
41
|
+
"@wtfalch/providers": ">=0.10.0",
|
|
42
42
|
"drizzle-orm": ">=0.45.2"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@wtfalch/auth": "0.16.0",
|
|
47
47
|
"@wtfalch/edge": "0.13.0",
|
|
48
48
|
"@wtfalch/email": "^0.20.0",
|
|
49
|
-
"@wtfalch/providers": "0.
|
|
49
|
+
"@wtfalch/providers": "0.10.0",
|
|
50
50
|
"ssh2": "1.17.0",
|
|
51
51
|
"zod": "4.6.5"
|
|
52
52
|
},
|