@wtfalch/foundry 0.56.0 → 0.58.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/desktop-callback.d.ts +17 -5
- package/dist/plan/desktop-callback.js +44 -13
- package/dist/plan/executor.d.ts +6 -10
- package/dist/plan/executor.js +17 -61
- package/dist/plan/platform-providers.js +3 -2
- package/dist/plan/signin-mail.d.ts +19 -5
- package/dist/plan/signin-mail.js +13 -7
- package/package.json +1 -1
|
@@ -11,15 +11,27 @@ export declare function desktopCallbackUri(host: string): string;
|
|
|
11
11
|
/** The URIs to add to an app's current list: current first, then each wanted one it lacks. */
|
|
12
12
|
export declare function unionRedirectUris(current: readonly string[], wanted: readonly string[]): string[];
|
|
13
13
|
/**
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* The redirect URIs a company web app signs in with: `/auth/callback` and the
|
|
15
|
+
* desktop callback on each of its hosts. Agora's own client holds only its
|
|
16
|
+
* `/callback`. `zitadelApp` creates the app with this list, and
|
|
17
|
+
* `zitadelDesktopCallback` adds what an existing app lacks.
|
|
18
|
+
*/
|
|
19
|
+
export declare function signInRedirectUris(org: DeclaredOrg, appKey: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The marker value for an app: its ZITADEL id and the URIs it must hold, in a
|
|
22
|
+
* fixed order. A changed hostname or a re-created app gives a different
|
|
23
|
+
* value, so the next plan names the app again.
|
|
24
|
+
*/
|
|
25
|
+
export declare function signInCallbackAppliedValue(appId: string, uris: readonly string[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Marker of company app `appKey`: `signInCallbackAppliedValue` once its
|
|
28
|
+
* sign-in callbacks were registered. A marker holding only the app id (the
|
|
29
|
+
* desktop-only form of #339) differs, so such an app is repaired once.
|
|
18
30
|
*/
|
|
19
31
|
export declare function zitadelAppDesktopCallbackAppliedScopeKey(appKey: string): string;
|
|
20
32
|
/**
|
|
21
33
|
* One `zitadel:desktop-callback` step names every live web sign-in app whose
|
|
22
|
-
*
|
|
34
|
+
* marker differs from the one its recorded id and wanted URIs give. Plans nothing when the host's ZITADEL
|
|
23
35
|
* client lacks the two redirect URI calls (`wired`), before the org and
|
|
24
36
|
* project are recorded, or while the platform's sign-in is not ready.
|
|
25
37
|
*/
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { agoraHostnameFor } from './agora.js';
|
|
2
|
+
import { bouleHostnameFor } from './boule.js';
|
|
3
|
+
import { appHostname, cmsStudioHostnameFor, entryHostnames } from './hostnames.js';
|
|
1
4
|
/**
|
|
2
5
|
* #339: the desktop app signs in through the company's web app, so every
|
|
3
6
|
* `@wtfalch/auth` web client foundry makes registers this path beside
|
|
@@ -12,19 +15,48 @@ export function unionRedirectUris(current, wanted) {
|
|
|
12
15
|
return [...new Set([...current, ...wanted])];
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* The redirect URIs a company web app signs in with: `/auth/callback` and the
|
|
19
|
+
* desktop callback on each of its hosts. Agora's own client holds only its
|
|
20
|
+
* `/callback`. `zitadelApp` creates the app with this list, and
|
|
21
|
+
* `zitadelDesktopCallback` adds what an existing app lacks.
|
|
22
|
+
*/
|
|
23
|
+
export function signInRedirectUris(org, appKey) {
|
|
24
|
+
if (appKey === 'agora')
|
|
25
|
+
return [`https://${agoraHostnameFor(org)}/callback`];
|
|
26
|
+
const authEntry = org.hosting.entries.find((e) => e.key === appKey && e.uses?.auth);
|
|
27
|
+
const hosts = authEntry
|
|
28
|
+
? entryHostnames(org, authEntry)
|
|
29
|
+
: [
|
|
30
|
+
appKey === 'cms' && org.site.enabled
|
|
31
|
+
? cmsStudioHostnameFor(org)
|
|
32
|
+
: appKey === 'boule'
|
|
33
|
+
? bouleHostnameFor(org)
|
|
34
|
+
: appHostname(org),
|
|
35
|
+
];
|
|
36
|
+
return [
|
|
37
|
+
...hosts.map((host) => `https://${host}/auth/callback`),
|
|
38
|
+
...hosts.map(desktopCallbackUri),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The marker value for an app: its ZITADEL id and the URIs it must hold, in a
|
|
43
|
+
* fixed order. A changed hostname or a re-created app gives a different
|
|
44
|
+
* value, so the next plan names the app again.
|
|
45
|
+
*/
|
|
46
|
+
export function signInCallbackAppliedValue(appId, uris) {
|
|
47
|
+
return `${appId} ${[...uris].sort().join(' ')}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Marker of company app `appKey`: `signInCallbackAppliedValue` once its
|
|
51
|
+
* sign-in callbacks were registered. A marker holding only the app id (the
|
|
52
|
+
* desktop-only form of #339) differs, so such an app is repaired once.
|
|
19
53
|
*/
|
|
20
54
|
export function zitadelAppDesktopCallbackAppliedScopeKey(appKey) {
|
|
21
55
|
return `zitadel_app_desktop_callback_applied.${appKey}`;
|
|
22
56
|
}
|
|
23
|
-
/** The fixed Agora client keeps its own `/callback`; it is not built on `@wtfalch/auth`. */
|
|
24
|
-
const NO_DESKTOP_CALLBACK_APPS = new Set(['agora']);
|
|
25
57
|
/**
|
|
26
58
|
* One `zitadel:desktop-callback` step names every live web sign-in app whose
|
|
27
|
-
*
|
|
59
|
+
* marker differs from the one its recorded id and wanted URIs give. Plans nothing when the host's ZITADEL
|
|
28
60
|
* client lacks the two redirect URI calls (`wired`), before the org and
|
|
29
61
|
* project are recorded, or while the platform's sign-in is not ready.
|
|
30
62
|
*/
|
|
@@ -32,10 +64,7 @@ export function planDesktopCallbacks(org, live, recordedIds, plannedSteps, authR
|
|
|
32
64
|
if (!wired || !authReady || !recordedIds.zitadel_org || !recordedIds.zitadel_project)
|
|
33
65
|
return null;
|
|
34
66
|
const apps = live
|
|
35
|
-
.filter((r) => r.system === 'zitadel' &&
|
|
36
|
-
r.kind === 'app' &&
|
|
37
|
-
r.config.appType === 'web' &&
|
|
38
|
-
!NO_DESKTOP_CALLBACK_APPS.has(r.key))
|
|
67
|
+
.filter((r) => r.system === 'zitadel' && r.kind === 'app' && r.config.appType === 'web')
|
|
39
68
|
.map((r) => r.key)
|
|
40
69
|
// An app this plan recreates gets its marker when it is made.
|
|
41
70
|
.filter((appKey) => !plannedSteps.some((s) => s.action.system === 'zitadel' &&
|
|
@@ -44,14 +73,16 @@ export function planDesktopCallbacks(org, live, recordedIds, plannedSteps, authR
|
|
|
44
73
|
s.action.desired.recreate === true))
|
|
45
74
|
.filter((appKey) => {
|
|
46
75
|
const appId = recordedIds[`zitadel:app:${appKey}`];
|
|
47
|
-
return (Boolean(appId) &&
|
|
76
|
+
return (Boolean(appId) &&
|
|
77
|
+
recordedIds[zitadelAppDesktopCallbackAppliedScopeKey(appKey)] !==
|
|
78
|
+
signInCallbackAppliedValue(appId, signInRedirectUris(org, appKey)));
|
|
48
79
|
});
|
|
49
80
|
if (apps.length === 0)
|
|
50
81
|
return null;
|
|
51
82
|
return {
|
|
52
83
|
action: { system: 'zitadel', kind: 'desktop-callback', key: org.id, desired: { apps } },
|
|
53
84
|
step: {
|
|
54
|
-
description: `register the
|
|
85
|
+
description: `register the sign-in callbacks for ${org.slug}'s sign-in apps (${apps.join(', ')})`,
|
|
55
86
|
destroys: false,
|
|
56
87
|
migrates: false,
|
|
57
88
|
},
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -2022,7 +2022,7 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2022
2022
|
/** Whether the held keys can store a new company's sign-in database CA and certificate (#746). */
|
|
2023
2023
|
ownSigninDbKeysPuttable(): boolean;
|
|
2024
2024
|
/**
|
|
2025
|
-
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText` and `
|
|
2025
|
+
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText`, `ensureLabelPolicy` and `ensureEmailOtpGenerator`
|
|
2026
2026
|
* (`provision.auth.signinMailWritable`). The port is a lazy proxy that answers any name, so it cannot be probed.
|
|
2027
2027
|
*/
|
|
2028
2028
|
ownSigninMailWritable(): boolean;
|
|
@@ -2212,20 +2212,16 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2212
2212
|
*/
|
|
2213
2213
|
private zitadelPostLogoutUris;
|
|
2214
2214
|
/**
|
|
2215
|
-
* #339: registers
|
|
2216
|
-
*
|
|
2217
|
-
*
|
|
2218
|
-
* union
|
|
2215
|
+
* #339: registers the sign-in callbacks (`signInRedirectUris`: `/auth/callback`
|
|
2216
|
+
* and `/auth/desktop/callback` per host, Agora's `/callback`) on each live web
|
|
2217
|
+
* sign-in app the planner named, keeping every redirect URI the app already
|
|
2218
|
+
* holds (the union of the current list and the wanted ones; a write only when
|
|
2219
|
+
* the union is larger). An app's marker (its app id and wanted URIs) is recorded and
|
|
2219
2220
|
* persisted only after its read and write resolved, so a failure leaves no
|
|
2220
2221
|
* marker and the next plan names the app again. The error names ids only,
|
|
2221
2222
|
* never the cause.
|
|
2222
2223
|
*/
|
|
2223
2224
|
private zitadelDesktopCallback;
|
|
2224
|
-
/**
|
|
2225
|
-
* The hostnames a company web app signs in at: the same rule `zitadelApp`
|
|
2226
|
-
* uses for the redirect URIs it creates the app with.
|
|
2227
|
-
*/
|
|
2228
|
-
private signInHostnames;
|
|
2229
2225
|
/**
|
|
2230
2226
|
* `plan/diff.ts`'s `planLoginHost` (#267): adds the org's own company
|
|
2231
2227
|
* login host (`action.desired.host`) to the ZITADEL instance's trusted
|
package/dist/plan/executor.js
CHANGED
|
@@ -22,7 +22,7 @@ import { previewsOn } from './coolify-previews.js';
|
|
|
22
22
|
import { CORE_FILES_COLLECTION_NAME, FILES_CORE_COLLECTION_SCOPE_KEY, ZITADEL_FILES_PROJECT_SCOPE_KEY, } from './core-files.js';
|
|
23
23
|
import { waitForHealthyVersion, waitForNewDeployment, } from './deploy-health.js';
|
|
24
24
|
import { DESKTOP_AUTH_APP_SCOPE_KEY, DESKTOP_AUTH_DEPLOYED_SCOPE_KEY, DESKTOP_AUTH_DOCKERFILE, DESKTOP_AUTH_HOME, DESKTOP_AUTH_ORIGIN_SCOPE_KEY, DESKTOP_AUTH_READY_PATH, DESKTOP_AUTH_READY_SCOPE_KEY, DESKTOP_AUTH_REDIRECT_SCOPE_KEY, DESKTOP_AUTH_REVISION_SCOPE_KEY, desktopAuthCallbackUri, desktopAuthEnv, desktopAuthInputs, desktopAuthRedirectUnsupported, desktopAuthRevision, } from './desktop-auth.js';
|
|
25
|
-
import {
|
|
25
|
+
import { signInCallbackAppliedValue, signInRedirectUris, unionRedirectUris, zitadelAppDesktopCallbackAppliedScopeKey, } from './desktop-callback.js';
|
|
26
26
|
import { PLATFORM_NETWORK, companyNetworkName, ensureDestination, findDestination, onPlatformNetwork, placed, placedByScope, } from './destinations.js';
|
|
27
27
|
import { DEFAULT_AI_LIMIT_MICROS } from './diff.js';
|
|
28
28
|
import { createEdgeAccess, isNotFound as isEdgeNotFound, } from './edge-access.js';
|
|
@@ -1014,7 +1014,7 @@ export class FoundryStepExecutor {
|
|
|
1014
1014
|
return Boolean(this.ports.ownServices?.provision.auth.heldKeys?.put);
|
|
1015
1015
|
}
|
|
1016
1016
|
/**
|
|
1017
|
-
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText` and `
|
|
1017
|
+
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText`, `ensureLabelPolicy` and `ensureEmailOtpGenerator`
|
|
1018
1018
|
* (`provision.auth.signinMailWritable`). The port is a lazy proxy that answers any name, so it cannot be probed.
|
|
1019
1019
|
*/
|
|
1020
1020
|
ownSigninMailWritable() {
|
|
@@ -1967,10 +1967,11 @@ export class FoundryStepExecutor {
|
|
|
1967
1967
|
return {};
|
|
1968
1968
|
}
|
|
1969
1969
|
/**
|
|
1970
|
-
* #339: registers
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
* union
|
|
1970
|
+
* #339: registers the sign-in callbacks (`signInRedirectUris`: `/auth/callback`
|
|
1971
|
+
* and `/auth/desktop/callback` per host, Agora's `/callback`) on each live web
|
|
1972
|
+
* sign-in app the planner named, keeping every redirect URI the app already
|
|
1973
|
+
* holds (the union of the current list and the wanted ones; a write only when
|
|
1974
|
+
* the union is larger). An app's marker (its app id and wanted URIs) is recorded and
|
|
1974
1975
|
* persisted only after its read and write resolved, so a failure leaves no
|
|
1975
1976
|
* marker and the next plan names the app again. The error names ids only,
|
|
1976
1977
|
* never the cause.
|
|
@@ -1991,7 +1992,7 @@ export class FoundryStepExecutor {
|
|
|
1991
1992
|
const appId = recorded[`zitadel:app:${appKey}`];
|
|
1992
1993
|
if (!appId)
|
|
1993
1994
|
continue;
|
|
1994
|
-
const wanted =
|
|
1995
|
+
const wanted = signInRedirectUris(org, appKey);
|
|
1995
1996
|
try {
|
|
1996
1997
|
const current = await read(zitadelOrgId, projectId, appId);
|
|
1997
1998
|
const union = unionRedirectUris(current, wanted);
|
|
@@ -1999,27 +2000,13 @@ export class FoundryStepExecutor {
|
|
|
1999
2000
|
await write(zitadelOrgId, projectId, appId, union);
|
|
2000
2001
|
}
|
|
2001
2002
|
catch {
|
|
2002
|
-
throw new Error(`zitadel:desktop-callback for ${org.slug}: registering the
|
|
2003
|
+
throw new Error(`zitadel:desktop-callback for ${org.slug}: registering the sign-in callbacks failed for app '${appKey}' (${appId})`);
|
|
2003
2004
|
}
|
|
2004
|
-
context.recordScopeId(zitadelAppDesktopCallbackAppliedScopeKey(appKey), appId);
|
|
2005
|
+
context.recordScopeId(zitadelAppDesktopCallbackAppliedScopeKey(appKey), signInCallbackAppliedValue(appId, wanted));
|
|
2005
2006
|
await context.persistScopeIds();
|
|
2006
2007
|
}
|
|
2007
2008
|
return {};
|
|
2008
2009
|
}
|
|
2009
|
-
/**
|
|
2010
|
-
* The hostnames a company web app signs in at: the same rule `zitadelApp`
|
|
2011
|
-
* uses for the redirect URIs it creates the app with.
|
|
2012
|
-
*/
|
|
2013
|
-
signInHostnames(org, appKey) {
|
|
2014
|
-
const authEntry = org.hosting.entries.find((e) => e.key === appKey && e.uses?.auth);
|
|
2015
|
-
if (appKey === 'cms' && org.site.enabled)
|
|
2016
|
-
return [cmsHostnameFor(org)];
|
|
2017
|
-
if (appKey === 'boule')
|
|
2018
|
-
return [bouleHostnameFor(org)];
|
|
2019
|
-
if (authEntry)
|
|
2020
|
-
return entryHostnames(org, authEntry);
|
|
2021
|
-
return [appHostname(org)];
|
|
2022
|
-
}
|
|
2023
2010
|
/**
|
|
2024
2011
|
* `plan/diff.ts`'s `planLoginHost` (#267): adds the org's own company
|
|
2025
2012
|
* login host (`action.desired.host`) to the ZITADEL instance's trusted
|
|
@@ -2701,55 +2688,24 @@ export class FoundryStepExecutor {
|
|
|
2701
2688
|
await this.ports.deliverSecret(`zitadel:app:${org.slug}:${action.key}`, created.clientSecret);
|
|
2702
2689
|
return { resourceId: created.appId };
|
|
2703
2690
|
}
|
|
2704
|
-
// #139: the fixed `cms` client (`diff.ts`'s `planCmsClient`) signs
|
|
2705
|
-
// people into the cms studio at `cmsHostnameFor(org)` -- `cms.<domain>`,
|
|
2706
|
-
// or `<slug>-cms.oikos.host` for a company without a domain -- not the
|
|
2707
|
-
// org's own app hostname. #164: the fixed `agora` client (`diff.ts`'s
|
|
2708
|
-
// `planAgoraClient`) is the same shape, always -- there is no
|
|
2709
|
-
// `org.site.enabled`-style guard to check, since Agora is always on.
|
|
2710
2691
|
// The Agora client stays `appType: 'web'`: providers' `createOidcApp`
|
|
2711
2692
|
// makes every non-native app a public client (authorization code + PKCE,
|
|
2712
2693
|
// `OIDC_AUTH_METHOD_TYPE_NONE`, no secret), which is what Agora's browser
|
|
2713
2694
|
// SPA needs. Providers cannot create ZITADEL's `USER_AGENT` app kind.
|
|
2714
|
-
// #161: the fixed `boule` client signs people into the org's Boule at its
|
|
2715
|
-
// own hostname, on the standard `/auth/callback` path (Boule is built on
|
|
2716
|
-
// `@wtfalch/auth` like every other web app here).
|
|
2717
|
-
const hostname = isCmsClient
|
|
2718
|
-
? cmsHostnameFor(org)
|
|
2719
|
-
: isAgoraClient
|
|
2720
|
-
? agoraHostnameFor(org)
|
|
2721
|
-
: isBouleClient
|
|
2722
|
-
? bouleHostnameFor(org)
|
|
2723
|
-
: appHostname(org);
|
|
2724
2695
|
// Native apps sign in via device code, with no redirect of their own
|
|
2725
2696
|
// (auth/scripts/provision.mjs:475-497, prior art cited in the decisions
|
|
2726
2697
|
// file) -- empty arrays rather than a web app's callback/logout URLs.
|
|
2727
|
-
//
|
|
2728
|
-
//
|
|
2729
|
-
// client needs `https://<agora address>/callback` as a redirect URI") --
|
|
2730
|
-
// Agora is not built on `@wtfalch/auth`'s `NextAuth` mount the way every
|
|
2731
|
-
// other web app this file provisions is.
|
|
2732
|
-
const redirectUri = isAgoraClient
|
|
2733
|
-
? `https://${hostname}/callback`
|
|
2734
|
-
: `https://${hostname}/auth/callback`;
|
|
2735
|
-
// An entry with `uses.auth` signs in at its own exact hostnames, one pair
|
|
2736
|
-
// of addresses each; a `*` adds none (`entryHostnames`).
|
|
2737
|
-
const hosts = authEntry ? entryHostnames(org, authEntry) : null;
|
|
2698
|
+
// `signInRedirectUris` holds each web app's hosts and callback paths (the
|
|
2699
|
+
// cms, Agora, Boule and entry hostnames; Agora's `/callback`).
|
|
2738
2700
|
const postLogoutUris = appType === 'native' ? [] : companyAppPostLogoutUris(org, action.key);
|
|
2739
|
-
//
|
|
2740
|
-
const
|
|
2701
|
+
// The same rule the repair step uses (`signInRedirectUris`).
|
|
2702
|
+
const signInUris = appType === 'web' ? signInRedirectUris(org, action.key) : [];
|
|
2741
2703
|
const created = await this.ports.zitadel.createOidcApp(zitadelOrgId, projectId, {
|
|
2742
2704
|
name: action.key,
|
|
2743
2705
|
appType,
|
|
2744
2706
|
redirectUris: appType === 'native'
|
|
2745
2707
|
? []
|
|
2746
|
-
:
|
|
2747
|
-
? [
|
|
2748
|
-
...hosts.map((host) => `https://${host}/auth/callback`),
|
|
2749
|
-
...desktopCallbacks,
|
|
2750
|
-
...(authEntry?.uses?.auth?.mcp ? [mcpCallbackUri(org)] : []),
|
|
2751
|
-
]
|
|
2752
|
-
: [redirectUri, ...desktopCallbacks],
|
|
2708
|
+
: [...signInUris, ...(authEntry?.uses?.auth?.mcp ? [mcpCallbackUri(org)] : [])],
|
|
2753
2709
|
postLogoutRedirectUris: postLogoutUris,
|
|
2754
2710
|
...(authEntry?.uses?.auth?.mcp ? { accessTokenType: 'jwt' } : {}),
|
|
2755
2711
|
});
|
|
@@ -2769,8 +2725,8 @@ export class FoundryStepExecutor {
|
|
|
2769
2725
|
if (appType === 'web') {
|
|
2770
2726
|
context.recordScopeId(zitadelAppPostLogoutAppliedScopeKey(action.key), postLogoutAppliedValue(created.appId, postLogoutUris));
|
|
2771
2727
|
}
|
|
2772
|
-
if (
|
|
2773
|
-
context.recordScopeId(zitadelAppDesktopCallbackAppliedScopeKey(action.key), created.appId);
|
|
2728
|
+
if (appType === 'web') {
|
|
2729
|
+
context.recordScopeId(zitadelAppDesktopCallbackAppliedScopeKey(action.key), signInCallbackAppliedValue(created.appId, signInUris));
|
|
2774
2730
|
}
|
|
2775
2731
|
return { resourceId: created.appId };
|
|
2776
2732
|
}
|
|
@@ -2551,11 +2551,12 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
2551
2551
|
throw new Error(`${label}: could not configure ZITADEL's SMTP provider for ${hostname}: ${cause}`);
|
|
2552
2552
|
}
|
|
2553
2553
|
// A company's own sign-in: its name and design in the code mail. Idempotent; the marker is
|
|
2554
|
-
// recorded after
|
|
2554
|
+
// recorded after all three writes, so a failure halfway is repeated.
|
|
2555
2555
|
if (input.mailBranding &&
|
|
2556
2556
|
(deps.signinMailWritable !== true ||
|
|
2557
2557
|
typeof mailZitadel.ensureEmailOtpMessageText !== 'function' ||
|
|
2558
|
-
typeof mailZitadel.ensureLabelPolicy !== 'function'
|
|
2558
|
+
typeof mailZitadel.ensureLabelPolicy !== 'function' ||
|
|
2559
|
+
typeof mailZitadel.ensureEmailOtpGenerator !== 'function')) {
|
|
2559
2560
|
console.info(`${label}: the host cannot write message texts, so the sign-in mail keeps ZITADEL's default text`);
|
|
2560
2561
|
}
|
|
2561
2562
|
else if (input.mailBranding) {
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
* The sign-in code mail of a company that runs its own sign-in: the company's name in the sender and
|
|
3
3
|
* the text, the platform's design tokens for the look. ZITADEL's email-code message is built from the
|
|
4
4
|
* instance's custom text and label policy; both are written through `PlatformZitadelMailBranding`
|
|
5
|
-
* (`@wtfalch/providers` 0.
|
|
5
|
+
* (`@wtfalch/providers` 0.14.0), each only when it differs. The code itself (six digits, five minutes)
|
|
6
|
+
* is the instance's email-OTP generator.
|
|
6
7
|
*
|
|
7
8
|
* The look: no company declares a design foundry can read (an `OrgTheme` lives in cms and reaches
|
|
8
9
|
* Agora, Boule and the site as data), so every company gets the login's house colours
|
|
9
10
|
* (`SIGNIN_MAIL_LABEL_POLICY`). No logo: ZITADEL takes it as a multipart upload the client does not make.
|
|
10
11
|
*/
|
|
11
12
|
/** Raise it when the text or the look changes, so an applied company plans its sign-in step again. */
|
|
12
|
-
export declare const AUTH_SIGNIN_MAIL_VERSION = "
|
|
13
|
+
export declare const AUTH_SIGNIN_MAIL_VERSION = "2";
|
|
13
14
|
/** `signinMailMarker` of the company name and version last written, recorded after both writes. */
|
|
14
15
|
export declare const PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY = "auth_signin_mail_applied";
|
|
15
|
-
/** The
|
|
16
|
+
/** The code the mail carries: six digits, valid five minutes (`123-456` in the mail, see `signinMailText`). */
|
|
17
|
+
export declare const SIGNIN_MAIL_CODE: {
|
|
18
|
+
readonly length: 6;
|
|
19
|
+
readonly expirySeconds: 300;
|
|
20
|
+
};
|
|
21
|
+
/** The three ZITADEL calls the sign-in mail needs. `@wtfalch/providers` 0.14.0's `ZitadelClient` has all. */
|
|
16
22
|
export interface PlatformZitadelMailBranding {
|
|
17
23
|
ensureEmailOtpMessageText(language: string, text: {
|
|
18
24
|
title: string;
|
|
@@ -38,6 +44,12 @@ export interface PlatformZitadelMailBranding {
|
|
|
38
44
|
}): Promise<{
|
|
39
45
|
changed: boolean;
|
|
40
46
|
}>;
|
|
47
|
+
ensureEmailOtpGenerator(generator: {
|
|
48
|
+
length: number;
|
|
49
|
+
expirySeconds: number;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
changed: boolean;
|
|
52
|
+
}>;
|
|
41
53
|
}
|
|
42
54
|
/** What the marker holds: the version and the company name, so a rename writes the mail again. */
|
|
43
55
|
export declare function signinMailMarker(companyName: string): string;
|
|
@@ -49,7 +61,9 @@ export declare function signinMailName(companyName: string): string;
|
|
|
49
61
|
export declare const SIGNIN_MAIL_LANGUAGE = "en";
|
|
50
62
|
/**
|
|
51
63
|
* English text. `{{.OTP}}` is the code variable of this message (ZITADEL's `otp.go` sets it; the link
|
|
52
|
-
* behind the button carries the code as `{{.Code}}` from the login's `urlTemplate`).
|
|
64
|
+
* behind the button carries the code as `{{.Code}}` from the login's `urlTemplate`). The greeting
|
|
65
|
+
* holds the code, split in the middle with Go's `slice` (`123-456`), because ZITADEL renders the
|
|
66
|
+
* greeting as the large heading, so the code is the first thing the reader sees. No greeting by
|
|
53
67
|
* name: the login fills names from the email address, so it would read "Wtfalch Wtfalch".
|
|
54
68
|
*/
|
|
55
69
|
export declare function signinMailText(companyName: string): {
|
|
@@ -77,7 +91,7 @@ export declare const SIGNIN_MAIL_LABEL_POLICY: {
|
|
|
77
91
|
readonly warnColorDark: "#FF9C8C";
|
|
78
92
|
readonly disableWatermark: true;
|
|
79
93
|
};
|
|
80
|
-
/** Writes the text and the
|
|
94
|
+
/** Writes the text, the look and the code, each only when it differs. Throws the client's fixed-text error. */
|
|
81
95
|
export declare function applySigninMail(zitadel: PlatformZitadelMailBranding, companyName: string): Promise<{
|
|
82
96
|
changed: boolean;
|
|
83
97
|
}>;
|
package/dist/plan/signin-mail.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
* The sign-in code mail of a company that runs its own sign-in: the company's name in the sender and
|
|
3
3
|
* the text, the platform's design tokens for the look. ZITADEL's email-code message is built from the
|
|
4
4
|
* instance's custom text and label policy; both are written through `PlatformZitadelMailBranding`
|
|
5
|
-
* (`@wtfalch/providers` 0.
|
|
5
|
+
* (`@wtfalch/providers` 0.14.0), each only when it differs. The code itself (six digits, five minutes)
|
|
6
|
+
* is the instance's email-OTP generator.
|
|
6
7
|
*
|
|
7
8
|
* The look: no company declares a design foundry can read (an `OrgTheme` lives in cms and reaches
|
|
8
9
|
* Agora, Boule and the site as data), so every company gets the login's house colours
|
|
9
10
|
* (`SIGNIN_MAIL_LABEL_POLICY`). No logo: ZITADEL takes it as a multipart upload the client does not make.
|
|
10
11
|
*/
|
|
11
12
|
/** Raise it when the text or the look changes, so an applied company plans its sign-in step again. */
|
|
12
|
-
export const AUTH_SIGNIN_MAIL_VERSION = '
|
|
13
|
+
export const AUTH_SIGNIN_MAIL_VERSION = '2';
|
|
13
14
|
/** `signinMailMarker` of the company name and version last written, recorded after both writes. */
|
|
14
15
|
export const PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY = 'auth_signin_mail_applied';
|
|
16
|
+
/** The code the mail carries: six digits, valid five minutes (`123-456` in the mail, see `signinMailText`). */
|
|
17
|
+
export const SIGNIN_MAIL_CODE = { length: 6, expirySeconds: 300 };
|
|
15
18
|
/** What the marker holds: the version and the company name, so a rename writes the mail again. */
|
|
16
19
|
export function signinMailMarker(companyName) {
|
|
17
20
|
return `${AUTH_SIGNIN_MAIL_VERSION}:${signinMailName(companyName)}`;
|
|
@@ -30,7 +33,9 @@ export function signinMailName(companyName) {
|
|
|
30
33
|
export const SIGNIN_MAIL_LANGUAGE = 'en';
|
|
31
34
|
/**
|
|
32
35
|
* English text. `{{.OTP}}` is the code variable of this message (ZITADEL's `otp.go` sets it; the link
|
|
33
|
-
* behind the button carries the code as `{{.Code}}` from the login's `urlTemplate`).
|
|
36
|
+
* behind the button carries the code as `{{.Code}}` from the login's `urlTemplate`). The greeting
|
|
37
|
+
* holds the code, split in the middle with Go's `slice` (`123-456`), because ZITADEL renders the
|
|
38
|
+
* greeting as the large heading, so the code is the first thing the reader sees. No greeting by
|
|
34
39
|
* name: the login fills names from the email address, so it would read "Wtfalch Wtfalch".
|
|
35
40
|
*/
|
|
36
41
|
export function signinMailText(companyName) {
|
|
@@ -39,8 +44,8 @@ export function signinMailText(companyName) {
|
|
|
39
44
|
title: 'Your sign-in code',
|
|
40
45
|
preHeader: `Your ${name} sign-in code`,
|
|
41
46
|
subject: `Your ${name} sign-in code`,
|
|
42
|
-
greeting: '
|
|
43
|
-
text: '
|
|
47
|
+
greeting: '{{slice .OTP 0 3}}-{{slice .OTP 3}}',
|
|
48
|
+
text: 'Enter this code to sign in. It works for five minutes. Or use the button to sign in on this device.',
|
|
44
49
|
buttonText: 'Sign in',
|
|
45
50
|
footerText: name,
|
|
46
51
|
};
|
|
@@ -61,9 +66,10 @@ export const SIGNIN_MAIL_LABEL_POLICY = {
|
|
|
61
66
|
warnColorDark: '#FF9C8C',
|
|
62
67
|
disableWatermark: true,
|
|
63
68
|
};
|
|
64
|
-
/** Writes the text and the
|
|
69
|
+
/** Writes the text, the look and the code, each only when it differs. Throws the client's fixed-text error. */
|
|
65
70
|
export async function applySigninMail(zitadel, companyName) {
|
|
66
71
|
const text = await zitadel.ensureEmailOtpMessageText(SIGNIN_MAIL_LANGUAGE, signinMailText(companyName));
|
|
67
72
|
const look = await zitadel.ensureLabelPolicy({ ...SIGNIN_MAIL_LABEL_POLICY });
|
|
68
|
-
|
|
73
|
+
const code = await zitadel.ensureEmailOtpGenerator({ ...SIGNIN_MAIL_CODE });
|
|
74
|
+
return { changed: text.changed || look.changed || code.changed };
|
|
69
75
|
}
|