@wtfalch/foundry 0.55.0 → 0.56.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.d.ts +2 -0
- package/dist/plan/diff.js +1 -0
- package/dist/plan/executor.d.ts +5 -0
- package/dist/plan/executor.js +8 -0
- package/dist/plan/own-diff.d.ts +2 -0
- package/dist/plan/own-diff.js +13 -1
- package/dist/plan/plan-apply.d.ts +2 -0
- package/dist/plan/plan-apply.js +1 -0
- package/dist/plan/platform-providers.d.ts +12 -1
- package/dist/plan/platform-providers.js +25 -1
- package/dist/plan/signin-mail.d.ts +83 -0
- package/dist/plan/signin-mail.js +69 -0
- package/package.json +1 -1
package/dist/plan/diff.d.ts
CHANGED
|
@@ -101,6 +101,8 @@ export interface PlanOrgOptions {
|
|
|
101
101
|
workerAppDbBackupConfigured?: boolean;
|
|
102
102
|
/** The executor can store the sign-in database's CA and certificate (`FoundryStepExecutor.ownSigninDbKeysPuttable`). Absent: true. */
|
|
103
103
|
ownSigninDbKeysPuttable?: boolean;
|
|
104
|
+
/** The executor's company ZITADEL port can write the sign-in mail (`FoundryStepExecutor.ownSigninMailWritable`). Absent: true. */
|
|
105
|
+
ownSigninMailWritable?: boolean;
|
|
104
106
|
/** The host supplies the backup bucket the sign-in database dumps into (`FoundryStepExecutor.ownSigninDbBackupConfigured`). Absent: true. */
|
|
105
107
|
ownSigninDbBackupConfigured?: boolean;
|
|
106
108
|
/** The own jobs admin token's held-key ref holds a value. */
|
package/dist/plan/diff.js
CHANGED
|
@@ -214,6 +214,7 @@ export function computePlanDetail(org, live, recordedIds = {}, agoraHost = NO_AG
|
|
|
214
214
|
now: (options.now ?? Date.now)(),
|
|
215
215
|
signinDbKeysPuttable: options.ownSigninDbKeysPuttable ?? true,
|
|
216
216
|
signinDbBackupConfigured: options.ownSigninDbBackupConfigured ?? true,
|
|
217
|
+
signinMailWritable: options.ownSigninMailWritable ?? true,
|
|
217
218
|
});
|
|
218
219
|
if (ownAuth.step)
|
|
219
220
|
plannedSteps.push(ownAuth.step);
|
package/dist/plan/executor.d.ts
CHANGED
|
@@ -2021,6 +2021,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
|
|
|
2021
2021
|
bouleArchonCredentialAvailable(): boolean;
|
|
2022
2022
|
/** Whether the held keys can store a new company's sign-in database CA and certificate (#746). */
|
|
2023
2023
|
ownSigninDbKeysPuttable(): boolean;
|
|
2024
|
+
/**
|
|
2025
|
+
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText` and `ensureLabelPolicy`
|
|
2026
|
+
* (`provision.auth.signinMailWritable`). The port is a lazy proxy that answers any name, so it cannot be probed.
|
|
2027
|
+
*/
|
|
2028
|
+
ownSigninMailWritable(): boolean;
|
|
2024
2029
|
/** Whether the held keys can store a Worker app database's CA and certificate. */
|
|
2025
2030
|
workerAppDbKeysPuttable(): boolean;
|
|
2026
2031
|
/** Whether the host supplies the backup bucket a Worker app database dumps into (the files host's bucket and key ports). */
|
package/dist/plan/executor.js
CHANGED
|
@@ -1013,6 +1013,13 @@ export class FoundryStepExecutor {
|
|
|
1013
1013
|
ownSigninDbKeysPuttable() {
|
|
1014
1014
|
return Boolean(this.ports.ownServices?.provision.auth.heldKeys?.put);
|
|
1015
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Whether the host says its company ZITADEL port has `ensureEmailOtpMessageText` and `ensureLabelPolicy`
|
|
1018
|
+
* (`provision.auth.signinMailWritable`). The port is a lazy proxy that answers any name, so it cannot be probed.
|
|
1019
|
+
*/
|
|
1020
|
+
ownSigninMailWritable() {
|
|
1021
|
+
return this.ports.ownServices?.provision.auth.signinMailWritable === true;
|
|
1022
|
+
}
|
|
1016
1023
|
/** Whether the held keys can store a Worker app database's CA and certificate. */
|
|
1017
1024
|
workerAppDbKeysPuttable() {
|
|
1018
1025
|
return typeof this.ports.heldKeys?.put === 'function';
|
|
@@ -7342,6 +7349,7 @@ export class FoundryStepExecutor {
|
|
|
7342
7349
|
// empty; the login-host step writes it once the apps' clients exist.
|
|
7343
7350
|
...(inAppSignIn(org) ? { signInLogin: { bindings: null } } : {}),
|
|
7344
7351
|
mcp: orgDeclaresMcp(org),
|
|
7352
|
+
mailBranding: { companyName: org.displayName },
|
|
7345
7353
|
});
|
|
7346
7354
|
if (refChanged) {
|
|
7347
7355
|
const now = scope.recordedIds();
|
package/dist/plan/own-diff.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export interface PlanOwnAuthOptions {
|
|
|
51
51
|
signinDbKeysPuttable?: boolean;
|
|
52
52
|
/** The host supplies the backup bucket the sign-in database dumps into. Default true. */
|
|
53
53
|
signinDbBackupConfigured?: boolean;
|
|
54
|
+
/** The host's ZITADEL port can write the sign-in mail text and look. Default true. False: the marker is not required. */
|
|
55
|
+
signinMailWritable?: boolean;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* An own org (`services.own`) runs its own sign-in: ZITADEL, the login app
|
package/dist/plan/own-diff.js
CHANGED
|
@@ -17,6 +17,7 @@ import { SIGN_IN_LOGIN_APP_SCOPE_KEY, inAppSignIn } from './sign-in.js';
|
|
|
17
17
|
import { signinDbBackupMissing } from './signin-db-backup.js';
|
|
18
18
|
import { SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY, signinDbCertDue } from './signin-db-tls.js';
|
|
19
19
|
import { SIGNIN_DB_TLS_SCOPE_KEY } from './signin-db.js';
|
|
20
|
+
import { PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY, signinMailMarker } from './signin-mail.js';
|
|
20
21
|
/** `unmanaged` key of the one line `planOwnJobs` reports. */
|
|
21
22
|
export const OWN_JOBS_BLOCKED_KEY = 'own-jobs';
|
|
22
23
|
/**
|
|
@@ -163,6 +164,9 @@ export function planOwnAuth(options) {
|
|
|
163
164
|
recordedIds[PLATFORM_AUTH_SMTP_SCOPE_KEY] &&
|
|
164
165
|
recordedIds[PLATFORM_AUTH_ZITADEL_RUN_OPTIONS_SCOPE_KEY] === AUTH_ZITADEL_RUN_OPTIONS &&
|
|
165
166
|
recordedIds[PLATFORM_AUTH_TUNNEL_ROUTES_SCOPE_KEY] === AUTH_TUNNEL_ROUTES_VERSION &&
|
|
167
|
+
// The code mail carries the company's name and design; an older apply, or a renamed company, writes it again.
|
|
168
|
+
(options.signinMailWritable === false ||
|
|
169
|
+
recordedIds[PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY] === signinMailMarker(org.displayName)) &&
|
|
166
170
|
// An entry with `mcp` needs the broker's database and secret (auth ADR 0002).
|
|
167
171
|
(!orgDeclaresMcp(org) || recordedIds[PLATFORM_AUTH_MCP_SCOPE_KEY] === '1') &&
|
|
168
172
|
(Boolean(recordedIds[PLATFORM_AUTH_LOGIN_CLIENT_USER_SCOPE_KEY]) || !brokerHoldsAnEntry) &&
|
|
@@ -175,7 +179,15 @@ export function planOwnAuth(options) {
|
|
|
175
179
|
!signinDbBackupMissing(recordedIds) &&
|
|
176
180
|
// ZITADEL was started on the TLS database and never seen healthy there: the step starts it again.
|
|
177
181
|
!recordedIds[SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY]) {
|
|
178
|
-
|
|
182
|
+
// Applied and otherwise done: say why the mail stays ZITADEL's stock text, once.
|
|
183
|
+
const mailMissing = options.signinMailWritable === false &&
|
|
184
|
+
recordedIds[PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY] !== signinMailMarker(org.displayName);
|
|
185
|
+
return {
|
|
186
|
+
step: null,
|
|
187
|
+
blocked: mailMissing
|
|
188
|
+
? `${org.slug}'s sign-in mail keeps ZITADEL's default text: the host cannot write message texts`
|
|
189
|
+
: null,
|
|
190
|
+
};
|
|
179
191
|
}
|
|
180
192
|
return {
|
|
181
193
|
blocked: null,
|
|
@@ -234,6 +234,8 @@ export interface StepExecutor {
|
|
|
234
234
|
ownSigninDbKeysPuttable?(): boolean;
|
|
235
235
|
/** Whether the host supplies the backup bucket the sign-in database dumps into (#746). Absent: true. */
|
|
236
236
|
ownSigninDbBackupConfigured?(): boolean;
|
|
237
|
+
/** Whether the host's company ZITADEL port can write the sign-in mail text and look. Absent: true. */
|
|
238
|
+
ownSigninMailWritable?(): boolean;
|
|
237
239
|
/**
|
|
238
240
|
* Whether the host's scoped ports for this own org include a files-organisation
|
|
239
241
|
* port. The planner plans no `files:organisation` step for an own org while
|
package/dist/plan/plan-apply.js
CHANGED
|
@@ -1363,6 +1363,7 @@ export class FoundryPlanApply {
|
|
|
1363
1363
|
workerAppDbBackupConfigured: this.executor.workerAppDbBackupConfigured?.() ?? true,
|
|
1364
1364
|
ownSigninDbKeysPuttable: this.executor.ownSigninDbKeysPuttable?.() ?? true,
|
|
1365
1365
|
ownSigninDbBackupConfigured: this.executor.ownSigninDbBackupConfigured?.() ?? true,
|
|
1366
|
+
ownSigninMailWritable: this.executor.ownSigninMailWritable?.() ?? true,
|
|
1366
1367
|
...(this.backupGate
|
|
1367
1368
|
? {
|
|
1368
1369
|
backupStorageUuid: (await this.platformDeps?.store.getServiceIds())?.[PLATFORM_BACKUP_S3_STORAGE_SCOPE_KEY] || null,
|
|
@@ -20,6 +20,7 @@ import type { PlatformDriftedSteps, PlatformStepExecutionContext, PlatformStepEx
|
|
|
20
20
|
import { type BrokerDeployRecord, PLATFORM_ZITADEL_ORG_SCOPE_KEY } from './platform-diff.js';
|
|
21
21
|
import type { PlatformOwnerInput } from './platform-org.js';
|
|
22
22
|
import type { SigninDbBackupPort } from './signin-db-backup.js';
|
|
23
|
+
import { type PlatformZitadelMailBranding } from './signin-mail.js';
|
|
23
24
|
export { PLATFORM_ZITADEL_ORG_SCOPE_KEY };
|
|
24
25
|
import { type PlatformGithubReleaseHookDeps } from './github-release-hook.js';
|
|
25
26
|
import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
@@ -134,6 +135,12 @@ export interface PlatformAuthSmtp {
|
|
|
134
135
|
password?: () => Promise<string>;
|
|
135
136
|
}
|
|
136
137
|
export interface PlatformAuthDeps {
|
|
138
|
+
/**
|
|
139
|
+
* The host's ZITADEL port can write the sign-in mail text and look (`PlatformZitadelMailBranding`,
|
|
140
|
+
* `@wtfalch/providers` 0.13.0). Only `true` writes it; otherwise a company keeps ZITADEL's stock text
|
|
141
|
+
* and the planner reports it. The port may be a proxy that answers any method name, so the host says it.
|
|
142
|
+
*/
|
|
143
|
+
signinMailWritable?: boolean;
|
|
137
144
|
/** Overrides the Lettermint SMTP relay ZITADEL is given. Absent, Lettermint exactly as before. */
|
|
138
145
|
smtp?: PlatformAuthSmtp;
|
|
139
146
|
coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'> &
|
|
@@ -150,7 +157,7 @@ export interface PlatformAuthDeps {
|
|
|
150
157
|
requestHeaders?: PlatformRequestHeadersFactory;
|
|
151
158
|
cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
|
|
152
159
|
/** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
|
|
153
|
-
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'reserveUserName' | 'closeSelfRegistration' | 'enforceSecondFactor' | 'inviteOrgOwner' | 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain' | 'setUserMetadata' | 'getUserMetadata'> & PlatformZitadelSmtp
|
|
160
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'reserveUserName' | 'closeSelfRegistration' | 'enforceSecondFactor' | 'inviteOrgOwner' | 'ensureDomainPolicy' | 'addOrgDomain' | 'removeOrgDomain' | 'setPrimaryOrgDomain' | 'setUserMetadata' | 'getUserMetadata'> & PlatformZitadelSmtp & Partial<PlatformZitadelMailBranding>;
|
|
154
161
|
/**
|
|
155
162
|
* Mails the owner's first-sign-in link to their personal address (the same
|
|
156
163
|
* port as `FoundryStepExecutorPorts.ownerInviteMail`). The link carries the
|
|
@@ -564,6 +571,10 @@ export interface AuthStackInput {
|
|
|
564
571
|
* database on the sign-in Postgres and `MCP_OAUTH_SECRET` before that entry reaches it. Absent: neither.
|
|
565
572
|
*/
|
|
566
573
|
mcp?: boolean;
|
|
574
|
+
/** A company's own sign-in: the code mail names this company (sender, text) and uses the platform's design. Absent: ZITADEL's stock mail. */
|
|
575
|
+
mailBranding?: {
|
|
576
|
+
companyName: string;
|
|
577
|
+
};
|
|
567
578
|
}
|
|
568
579
|
/**
|
|
569
580
|
* #168: makes `coolify:app` keyed {@link PLATFORM_AUTH_KEY} true -- a Coolify
|
|
@@ -36,6 +36,7 @@ import { ensureSignInOrg, inviteOwnerWithLink, signInOrgDomain } from './sign-in
|
|
|
36
36
|
import { SIGN_IN_BASE_PATH, SIGN_IN_BUILD_ARG_ENV, SIGN_IN_LOGIN_APP_SCOPE_KEY, SIGN_IN_UPSTREAM_ENV, inAppOnSharedLogin, signInLoginNames, signInUpstream, } from './sign-in.js';
|
|
37
37
|
import { SIGNIN_DB_ZITADEL_PENDING_SCOPE_KEY } from './signin-db-tls.js';
|
|
38
38
|
import { SIGNIN_DB_APP_SCOPE_KEY, SIGNIN_DB_TLS_SCOPE_KEY, dockerfileAppCreator, ensureSigninDbCaFile, provisionSigninDb, signinDbDsn, waitHealthy, withCallTimeout, } from './signin-db.js';
|
|
39
|
+
import { PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY, applySigninMail, signinMailMarker, signinMailName, } from './signin-mail.js';
|
|
39
40
|
export { PLATFORM_ZITADEL_ORG_SCOPE_KEY };
|
|
40
41
|
import { githubReleaseHookPlanInputs, provisionGithubReleaseHook, } from './github-release-hook.js';
|
|
41
42
|
import { applyCallsSfuPlatformStep, callsSfuAppliedFingerprint, } from './platform-sfu.js';
|
|
@@ -2531,7 +2532,9 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
2531
2532
|
user: smtpTarget.user,
|
|
2532
2533
|
password: smtpPassword,
|
|
2533
2534
|
senderAddress: input.smtpSender,
|
|
2534
|
-
senderName:
|
|
2535
|
+
senderName: input.mailBranding
|
|
2536
|
+
? signinMailName(input.mailBranding.companyName)
|
|
2537
|
+
: PLATFORM_AUTH_MAIL_SENDER_NAME,
|
|
2535
2538
|
tls: smtpTarget.tls,
|
|
2536
2539
|
description: target.kind === 'platform'
|
|
2537
2540
|
? PLATFORM_AUTH_SMTP_DESCRIPTION
|
|
@@ -2547,6 +2550,27 @@ export async function provisionAuthStack(scope, deps, target, input) {
|
|
|
2547
2550
|
]);
|
|
2548
2551
|
throw new Error(`${label}: could not configure ZITADEL's SMTP provider for ${hostname}: ${cause}`);
|
|
2549
2552
|
}
|
|
2553
|
+
// A company's own sign-in: its name and design in the code mail. Idempotent; the marker is
|
|
2554
|
+
// recorded after both writes, so a failure halfway is repeated.
|
|
2555
|
+
if (input.mailBranding &&
|
|
2556
|
+
(deps.signinMailWritable !== true ||
|
|
2557
|
+
typeof mailZitadel.ensureEmailOtpMessageText !== 'function' ||
|
|
2558
|
+
typeof mailZitadel.ensureLabelPolicy !== 'function')) {
|
|
2559
|
+
console.info(`${label}: the host cannot write message texts, so the sign-in mail keeps ZITADEL's default text`);
|
|
2560
|
+
}
|
|
2561
|
+
else if (input.mailBranding) {
|
|
2562
|
+
try {
|
|
2563
|
+
await applySigninMail(mailZitadel, input.mailBranding.companyName);
|
|
2564
|
+
}
|
|
2565
|
+
catch (error) {
|
|
2566
|
+
const cause = redactSecrets(error instanceof Error ? error.message : String(error), [
|
|
2567
|
+
mailToken,
|
|
2568
|
+
...(smtpPassword ? [smtpPassword] : []),
|
|
2569
|
+
]);
|
|
2570
|
+
throw new Error(`${label}: could not set the sign-in mail text and look for ${hostname}: ${cause}`);
|
|
2571
|
+
}
|
|
2572
|
+
await record(PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY, signinMailMarker(input.mailBranding.companyName));
|
|
2573
|
+
}
|
|
2550
2574
|
const inviteRecorded = scope.recordedIds()[PLATFORM_AUTH_OWNER_INVITE_SCOPE_KEY];
|
|
2551
2575
|
const inviteOrgRecorded = scope.recordedIds()[PLATFORM_AUTH_OWNER_INVITE_ORG_SCOPE_KEY];
|
|
2552
2576
|
// An invite recorded for another org (the `foundry` org, before the owner moved) is made again.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in code mail of a company that runs its own sign-in: the company's name in the sender and
|
|
3
|
+
* the text, the platform's design tokens for the look. ZITADEL's email-code message is built from the
|
|
4
|
+
* instance's custom text and label policy; both are written through `PlatformZitadelMailBranding`
|
|
5
|
+
* (`@wtfalch/providers` 0.13.0), each only when it differs.
|
|
6
|
+
*
|
|
7
|
+
* The look: no company declares a design foundry can read (an `OrgTheme` lives in cms and reaches
|
|
8
|
+
* Agora, Boule and the site as data), so every company gets the login's house colours
|
|
9
|
+
* (`SIGNIN_MAIL_LABEL_POLICY`). No logo: ZITADEL takes it as a multipart upload the client does not make.
|
|
10
|
+
*/
|
|
11
|
+
/** 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 = "1";
|
|
13
|
+
/** `signinMailMarker` of the company name and version last written, recorded after both writes. */
|
|
14
|
+
export declare const PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY = "auth_signin_mail_applied";
|
|
15
|
+
/** The two ZITADEL calls the sign-in mail needs. `@wtfalch/providers` 0.13.0's `ZitadelClient` has both. */
|
|
16
|
+
export interface PlatformZitadelMailBranding {
|
|
17
|
+
ensureEmailOtpMessageText(language: string, text: {
|
|
18
|
+
title: string;
|
|
19
|
+
preHeader: string;
|
|
20
|
+
subject: string;
|
|
21
|
+
greeting: string;
|
|
22
|
+
text: string;
|
|
23
|
+
buttonText: string;
|
|
24
|
+
footerText: string;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
changed: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
ensureLabelPolicy(policy: {
|
|
29
|
+
primaryColor: string;
|
|
30
|
+
backgroundColor: string;
|
|
31
|
+
fontColor: string;
|
|
32
|
+
warnColor: string;
|
|
33
|
+
primaryColorDark: string;
|
|
34
|
+
backgroundColorDark: string;
|
|
35
|
+
fontColorDark: string;
|
|
36
|
+
warnColorDark: string;
|
|
37
|
+
disableWatermark: boolean;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
changed: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/** What the marker holds: the version and the company name, so a rename writes the mail again. */
|
|
43
|
+
export declare function signinMailMarker(companyName: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* The name as it goes into the sender and the text: one line, and no `{{`, which ZITADEL would run
|
|
46
|
+
* as a template action.
|
|
47
|
+
*/
|
|
48
|
+
export declare function signinMailName(companyName: string): string;
|
|
49
|
+
export declare const SIGNIN_MAIL_LANGUAGE = "en";
|
|
50
|
+
/**
|
|
51
|
+
* 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`). No greeting by
|
|
53
|
+
* name: the login fills names from the email address, so it would read "Wtfalch Wtfalch".
|
|
54
|
+
*/
|
|
55
|
+
export declare function signinMailText(companyName: string): {
|
|
56
|
+
title: string;
|
|
57
|
+
preHeader: string;
|
|
58
|
+
subject: string;
|
|
59
|
+
greeting: string;
|
|
60
|
+
text: string;
|
|
61
|
+
buttonText: string;
|
|
62
|
+
footerText: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The login's house colours as ZITADEL's label policy. The login reads the instance label policy for
|
|
66
|
+
* its own page colours and falls back to these when a value is empty (`login/src/helpers/colors.ts`
|
|
67
|
+
* in wtfalch/auth), so writing them keeps the sign-in window as it is and makes the mail match it.
|
|
68
|
+
*/
|
|
69
|
+
export declare const SIGNIN_MAIL_LABEL_POLICY: {
|
|
70
|
+
readonly primaryColor: "#B23A0C";
|
|
71
|
+
readonly backgroundColor: "#F6F3EC";
|
|
72
|
+
readonly fontColor: "#1D1B16";
|
|
73
|
+
readonly warnColor: "#B3261E";
|
|
74
|
+
readonly primaryColorDark: "#FF8A4A";
|
|
75
|
+
readonly backgroundColorDark: "#17130E";
|
|
76
|
+
readonly fontColorDark: "#EDE8DD";
|
|
77
|
+
readonly warnColorDark: "#FF9C8C";
|
|
78
|
+
readonly disableWatermark: true;
|
|
79
|
+
};
|
|
80
|
+
/** Writes the text and the look, each only when it differs. Throws the client's fixed-text error. */
|
|
81
|
+
export declare function applySigninMail(zitadel: PlatformZitadelMailBranding, companyName: string): Promise<{
|
|
82
|
+
changed: boolean;
|
|
83
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in code mail of a company that runs its own sign-in: the company's name in the sender and
|
|
3
|
+
* the text, the platform's design tokens for the look. ZITADEL's email-code message is built from the
|
|
4
|
+
* instance's custom text and label policy; both are written through `PlatformZitadelMailBranding`
|
|
5
|
+
* (`@wtfalch/providers` 0.13.0), each only when it differs.
|
|
6
|
+
*
|
|
7
|
+
* The look: no company declares a design foundry can read (an `OrgTheme` lives in cms and reaches
|
|
8
|
+
* Agora, Boule and the site as data), so every company gets the login's house colours
|
|
9
|
+
* (`SIGNIN_MAIL_LABEL_POLICY`). No logo: ZITADEL takes it as a multipart upload the client does not make.
|
|
10
|
+
*/
|
|
11
|
+
/** 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 = '1';
|
|
13
|
+
/** `signinMailMarker` of the company name and version last written, recorded after both writes. */
|
|
14
|
+
export const PLATFORM_AUTH_SIGNIN_MAIL_SCOPE_KEY = 'auth_signin_mail_applied';
|
|
15
|
+
/** What the marker holds: the version and the company name, so a rename writes the mail again. */
|
|
16
|
+
export function signinMailMarker(companyName) {
|
|
17
|
+
return `${AUTH_SIGNIN_MAIL_VERSION}:${signinMailName(companyName)}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The name as it goes into the sender and the text: one line, and no `{{`, which ZITADEL would run
|
|
21
|
+
* as a template action.
|
|
22
|
+
*/
|
|
23
|
+
export function signinMailName(companyName) {
|
|
24
|
+
return companyName
|
|
25
|
+
.replace(/[\p{Cc}\s]+/gu, ' ')
|
|
26
|
+
.replaceAll('{{', '{ {')
|
|
27
|
+
.replaceAll('}}', '} }')
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
export const SIGNIN_MAIL_LANGUAGE = 'en';
|
|
31
|
+
/**
|
|
32
|
+
* 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`). No greeting by
|
|
34
|
+
* name: the login fills names from the email address, so it would read "Wtfalch Wtfalch".
|
|
35
|
+
*/
|
|
36
|
+
export function signinMailText(companyName) {
|
|
37
|
+
const name = signinMailName(companyName);
|
|
38
|
+
return {
|
|
39
|
+
title: 'Your sign-in code',
|
|
40
|
+
preHeader: `Your ${name} sign-in code`,
|
|
41
|
+
subject: `Your ${name} sign-in code`,
|
|
42
|
+
greeting: 'Hello,',
|
|
43
|
+
text: 'Your code is {{.OTP}}. It works for five minutes. Or use the button to sign in on this device.',
|
|
44
|
+
buttonText: 'Sign in',
|
|
45
|
+
footerText: name,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The login's house colours as ZITADEL's label policy. The login reads the instance label policy for
|
|
50
|
+
* its own page colours and falls back to these when a value is empty (`login/src/helpers/colors.ts`
|
|
51
|
+
* in wtfalch/auth), so writing them keeps the sign-in window as it is and makes the mail match it.
|
|
52
|
+
*/
|
|
53
|
+
export const SIGNIN_MAIL_LABEL_POLICY = {
|
|
54
|
+
primaryColor: '#B23A0C',
|
|
55
|
+
backgroundColor: '#F6F3EC',
|
|
56
|
+
fontColor: '#1D1B16',
|
|
57
|
+
warnColor: '#B3261E',
|
|
58
|
+
primaryColorDark: '#FF8A4A',
|
|
59
|
+
backgroundColorDark: '#17130E',
|
|
60
|
+
fontColorDark: '#EDE8DD',
|
|
61
|
+
warnColorDark: '#FF9C8C',
|
|
62
|
+
disableWatermark: true,
|
|
63
|
+
};
|
|
64
|
+
/** Writes the text and the look, each only when it differs. Throws the client's fixed-text error. */
|
|
65
|
+
export async function applySigninMail(zitadel, companyName) {
|
|
66
|
+
const text = await zitadel.ensureEmailOtpMessageText(SIGNIN_MAIL_LANGUAGE, signinMailText(companyName));
|
|
67
|
+
const look = await zitadel.ensureLabelPolicy({ ...SIGNIN_MAIL_LABEL_POLICY });
|
|
68
|
+
return { changed: text.changed || look.changed };
|
|
69
|
+
}
|