@camstack/addon-auth 1.2.130 → 1.2.132

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.
@@ -0,0 +1,16 @@
1
+ //#region src/hub-browser-origin.ts
2
+ async function hubBrowserOrigin(localNetwork, logger) {
3
+ try {
4
+ return (await localNetwork.getAddresses.query()).roles.publicEndpoint.resolved;
5
+ } catch (err) {
6
+ logger.warn("hub address unavailable from local-network — using the localhost default", { meta: { error: err instanceof Error ? err.message : String(err) } });
7
+ return null;
8
+ }
9
+ }
10
+ //#endregion
11
+ Object.defineProperty(exports, "hubBrowserOrigin", {
12
+ enumerable: true,
13
+ get: function() {
14
+ return hubBrowserOrigin;
15
+ }
16
+ });
@@ -0,0 +1,11 @@
1
+ //#region src/hub-browser-origin.ts
2
+ async function hubBrowserOrigin(localNetwork, logger) {
3
+ try {
4
+ return (await localNetwork.getAddresses.query()).roles.publicEndpoint.resolved;
5
+ } catch (err) {
6
+ logger.warn("hub address unavailable from local-network — using the localhost default", { meta: { error: err instanceof Error ? err.message : String(err) } });
7
+ return null;
8
+ }
9
+ }
10
+ //#endregion
11
+ export { hubBrowserOrigin as t };
@@ -3,7 +3,42 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../chunk-Cek0wNdY.js");
6
- const require_hub_browser_origin = require("../hub-browser-origin-C-u2l9VC.js");
6
+ const require_dist = require("../dist-DjCeMP9o.js");
7
+ const require_hub_browser_origin = require("../hub-browser-origin-DohQUFPz.js");
8
+ //#region src/magic-link/retire-enabled-flag.ts
9
+ /** The retired settings key. */
10
+ var RETIRED_ENABLED_KEY = "enabled";
11
+ /** Store key marking the retirement as done. Not a schema field. */
12
+ var RETIREMENT_MARKER_KEY = "enabledFlagRetired";
13
+ function deferred(reason) {
14
+ return {
15
+ kind: "deferred",
16
+ reason
17
+ };
18
+ }
19
+ function isMarked(read) {
20
+ return read.kind === "present" && read.value === true;
21
+ }
22
+ async function retireEnabledFlag(deps) {
23
+ const marker = await deps.readStoredKey(RETIREMENT_MARKER_KEY);
24
+ if (marker.kind === "unreadable") return deferred("settings store unreadable (marker)");
25
+ if (isMarked(marker)) return { kind: "already-retired" };
26
+ const flag = await deps.readStoredKey(RETIRED_ENABLED_KEY);
27
+ if (flag.kind === "unreadable") return deferred("settings store unreadable (enabled)");
28
+ const wasOn = flag.kind === "present" && flag.value === true;
29
+ if (!wasOn) try {
30
+ await deps.disableAtCapability();
31
+ } catch (err) {
32
+ return deferred(`capability toggle refused: ${err instanceof Error ? err.message : String(err)}`);
33
+ }
34
+ await deps.commit();
35
+ if (!isMarked(await deps.readStoredKey("enabledFlagRetired"))) return deferred("retirement write did not land");
36
+ return {
37
+ kind: "retired",
38
+ capDisabled: !wasOn
39
+ };
40
+ }
41
+ //#endregion
7
42
  //#region src/magic-link/auth-magic-link.addon.ts
8
43
  /**
9
44
  * Magic-link authentication addon.
@@ -18,10 +53,10 @@ const require_hub_browser_origin = require("../hub-browser-origin-C-u2l9VC.js");
18
53
  * 3. Email click hits `/login?bridge=…` → 302 to `/api/auth/sso/finish?bridge=…`
19
54
  * → hub verifies the bridge token and mints the real session.
20
55
  *
21
- * Delivery is pluggable. This addon ships a "log-only" mode by default
22
- * (writes the URL to the addon log so operators can copy it during
23
- * setup before SMTP is configured). Production deployments configure
24
- * the SMTP relay via global settings.
56
+ * Delivery is pluggable. This addon ships a "log-only" mode by default,
57
+ * which records that a link was requested but never writes the link
58
+ * itself (it is a login credential — D783). Production deployments
59
+ * configure the SMTP relay via global settings.
25
60
  *
26
61
  * Open follow-ups:
27
62
  * - Wire `nodemailer` for real SMTP delivery (peer dep; the framework
@@ -29,16 +64,30 @@ const require_hub_browser_origin = require("../hub-browser-origin-C-u2l9VC.js");
29
64
  * - Throttle requests per-email (rate-limit window).
30
65
  * - Auto-provision unknown emails (currently rejected for safety).
31
66
  */
67
+ /** Capability whose per-provider toggle is magic-link's ONE enable (Identity → Authentication). */
68
+ var GOVERNING_CAPABILITY = "auth-provider";
69
+ /** Backoff between retirement attempts — the first ones are refused until the
70
+ * provider registration reaches the hub. After the last, the next boot retries. */
71
+ var RETIREMENT_RETRY_DELAYS_MS = [
72
+ 1e3,
73
+ 2e3,
74
+ 5e3,
75
+ 1e4,
76
+ 3e4,
77
+ 6e4
78
+ ];
32
79
  var DEFAULT_CONFIG = {
33
- enabled: false,
34
80
  displayName: "Email magic link",
35
- icon: "mail",
81
+ icon: "mailbox",
36
82
  publicOrigin: "",
37
83
  deliveryMode: "log-only",
38
84
  defaultRole: "viewer",
39
85
  ttlSec: 600
40
86
  };
41
- var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
87
+ var AuthMagicLinkAddon = class extends require_dist.BaseAddon {
88
+ enabledFlagRetired = false;
89
+ retirementTimer = null;
90
+ shuttingDown = false;
42
91
  constructor() {
43
92
  super({ ...DEFAULT_CONFIG });
44
93
  }
@@ -53,7 +102,7 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
53
102
  throw new Error("Magic link does not use callback — the email link redirects to /api/auth/sso/finish directly");
54
103
  }
55
104
  };
56
- const routeProvider = require_hub_browser_origin.buildAddonRouteProvider("auth-magic-link", [
105
+ const routeProvider = require_dist.buildAddonRouteProvider("auth-magic-link", [
57
106
  {
58
107
  method: "GET",
59
108
  path: "/start",
@@ -78,32 +127,88 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
78
127
  ]);
79
128
  const loginMethodProvider = { getLoginMethods: async () => this.buildLoginMethods() };
80
129
  this.ctx.logger.info("Magic-link auth provider initialized", { meta: { deliveryMode: this.config.deliveryMode } });
130
+ this.shuttingDown = false;
131
+ this.attemptEnabledFlagRetirement(0);
81
132
  return [
82
133
  {
83
- capability: require_hub_browser_origin.authProviderCapability,
134
+ capability: require_dist.authProviderCapability,
84
135
  provider: authProvider
85
136
  },
86
137
  {
87
- capability: require_hub_browser_origin.addonRoutesCapability,
138
+ capability: require_dist.addonRoutesCapability,
88
139
  provider: routeProvider
89
140
  },
90
141
  {
91
- capability: require_hub_browser_origin.loginMethodCapability,
142
+ capability: require_dist.loginMethodCapability,
92
143
  provider: loginMethodProvider
93
144
  }
94
145
  ];
95
146
  }
96
147
  buildLoginMethods() {
97
- if (!this.config.enabled) return [];
148
+ if (!this.enabledFlagRetired) return [];
98
149
  return [{
99
150
  kind: "redirect",
100
151
  id: "auth-magic-link",
101
152
  label: this.config.displayName || DEFAULT_CONFIG.displayName,
102
- icon: this.config.icon || DEFAULT_CONFIG.icon,
153
+ icon: require_dist.isDisplayIconKey(this.config.icon) ? this.config.icon : DEFAULT_CONFIG.icon,
103
154
  startUrl: "/addon/auth-magic-link/start",
104
155
  stage: "primary"
105
156
  }];
106
157
  }
158
+ async onShutdown() {
159
+ this.shuttingDown = true;
160
+ if (this.retirementTimer !== null) clearTimeout(this.retirementTimer);
161
+ this.retirementTimer = null;
162
+ }
163
+ /** One attempt of the D777 carry-over; reschedules itself while deferred. */
164
+ async attemptEnabledFlagRetirement(attempt) {
165
+ if (this.shuttingDown) return;
166
+ let outcome;
167
+ try {
168
+ outcome = await retireEnabledFlag({
169
+ readStoredKey: (key) => this.readStoredGlobalKey(key),
170
+ disableAtCapability: async () => {
171
+ await this.ctx.api.addons.setCapabilityProviderEnabled.mutate({
172
+ capName: GOVERNING_CAPABILITY,
173
+ addonId: this.ctx.id,
174
+ enabled: false
175
+ });
176
+ },
177
+ commit: async () => {
178
+ const patch = {
179
+ [RETIRED_ENABLED_KEY]: void 0,
180
+ [RETIREMENT_MARKER_KEY]: true
181
+ };
182
+ await this.updateGlobalSettings(patch);
183
+ }
184
+ });
185
+ } catch (err) {
186
+ outcome = {
187
+ kind: "deferred",
188
+ reason: require_dist.errMsg(err)
189
+ };
190
+ }
191
+ if (outcome.kind !== "deferred") {
192
+ this.enabledFlagRetired = true;
193
+ if (outcome.kind === "retired") this.ctx.logger.info(outcome.capDisabled ? "Magic-link: the retired `enabled` setting was off — the provider is now DISABLED on Identity → Authentication; enable it there to offer magic-link login." : "Magic-link: the retired `enabled` setting was on — the provider stays enabled on Identity → Authentication.");
194
+ return;
195
+ }
196
+ const delay = RETIREMENT_RETRY_DELAYS_MS[attempt];
197
+ if (delay === void 0) {
198
+ this.ctx.logger.warn("Magic-link: carrying the retired `enabled` setting over to the capability toggle failed — login button withheld until the next boot", { meta: { reason: outcome.reason } });
199
+ return;
200
+ }
201
+ this.ctx.logger.debug("Magic-link: enabled-flag retirement deferred", { meta: {
202
+ reason: outcome.reason,
203
+ attempt,
204
+ retryInMs: delay
205
+ } });
206
+ this.retirementTimer = setTimeout(() => {
207
+ this.retirementTimer = null;
208
+ this.attemptEnabledFlagRetirement(attempt + 1);
209
+ }, delay);
210
+ this.retirementTimer.unref?.();
211
+ }
107
212
  globalSettingsSchema() {
108
213
  return this.schema({ sections: [{
109
214
  id: "magic-link",
@@ -111,13 +216,6 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
111
216
  description: "Passwordless email login. The user types an email; the hub mails them a one-time link valid for the configured TTL.",
112
217
  columns: 1,
113
218
  fields: [
114
- this.field({
115
- type: "boolean",
116
- key: "enabled",
117
- label: "Enabled",
118
- description: "Offer the magic-link button on the login page. Leave off until email delivery is configured.",
119
- default: DEFAULT_CONFIG.enabled
120
- }),
121
219
  this.field({
122
220
  type: "text",
123
221
  key: "displayName",
@@ -125,9 +223,10 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
125
223
  default: DEFAULT_CONFIG.displayName
126
224
  }),
127
225
  this.field({
128
- type: "text",
226
+ type: "icon",
129
227
  key: "icon",
130
228
  label: "Icon",
229
+ description: "Shown on the login button.",
131
230
  default: DEFAULT_CONFIG.icon
132
231
  }),
133
232
  this.field({
@@ -141,7 +240,7 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
141
240
  type: "select",
142
241
  key: "deliveryMode",
143
242
  label: "Delivery mode",
144
- description: "log-only writes the URL to the addon log (setup only). smtp uses the configured SMTP relay.",
243
+ description: "log-only records the request in the addon log but never the link (it is a login credential). smtp uses the configured SMTP relay.",
145
244
  default: DEFAULT_CONFIG.deliveryMode,
146
245
  options: [{
147
246
  value: "log-only",
@@ -184,7 +283,7 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
184
283
  try {
185
284
  users = await this.ctx.api.userManagement.listUsers.query();
186
285
  } catch (err) {
187
- this.ctx.logger.warn("Magic-link user lookup failed (user-management cap unavailable)", { meta: { message: require_hub_browser_origin.errMsg(err) } });
286
+ this.ctx.logger.warn("Magic-link user lookup failed (user-management cap unavailable)", { meta: { message: require_dist.errMsg(err) } });
188
287
  return null;
189
288
  }
190
289
  const needle = email.trim().toLowerCase();
@@ -228,7 +327,7 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
228
327
  <input id="email" name="email" type="email" autocomplete="email" required autofocus placeholder="you@example.com">
229
328
  <button id="b" type="submit">Send login link</button>
230
329
  <div class="msg" id="m" hidden></div>
231
- <p style="margin-top:1rem"><a href="/admin/login">Back to sign in</a></p>
330
+ <p style="margin-top:1rem"><a href="/login">Back to sign in</a></p>
232
331
  </form>
233
332
  <script>
234
333
  var f=document.getElementById('f'),b=document.getElementById('b'),m=document.getElementById('m');
@@ -273,20 +372,20 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
273
372
  ttlSec: this.config.ttlSec || DEFAULT_CONFIG.ttlSec
274
373
  }));
275
374
  } catch (err) {
276
- this.ctx.logger.error("Magic-link bridge-token minting failed (sso-bridge unavailable)", { meta: { message: require_hub_browser_origin.errMsg(err) } });
375
+ this.ctx.logger.error("Magic-link bridge-token minting failed (sso-bridge unavailable)", { meta: { message: require_dist.errMsg(err) } });
277
376
  reply.code(503);
278
377
  reply.send({ error: "sso-bridge capability unavailable" });
279
378
  return;
280
379
  }
281
- const link = `${await this.buildPublicOrigin()}/addon/auth-magic-link/login?bridge=${encodeURIComponent(token)}`;
282
- if (this.config.deliveryMode === "log-only") this.ctx.logger.warn("Magic link generated (log-only mode — NOT delivered via email)", { meta: {
380
+ `${await this.buildPublicOrigin()}${encodeURIComponent(token)}`;
381
+ if (this.config.deliveryMode === "log-only") this.ctx.logger.warn("Magic link generated — not delivered (log-only mode, link withheld)", { meta: {
283
382
  email,
284
- link,
285
- ttlSec: this.config.ttlSec
383
+ ttlSec: this.config.ttlSec,
384
+ delivered: false
286
385
  } });
287
- else this.ctx.logger.warn("Magic link delivery: SMTP mode requested but no smtp-provider cap registered. Falling back to log.", { meta: {
386
+ else this.ctx.logger.warn("Magic link not delivered: SMTP mode requested but no smtp-provider cap registered", { meta: {
288
387
  email,
289
- link
388
+ delivered: false
290
389
  } });
291
390
  reply.code(200);
292
391
  reply.send(ok);
@@ -294,7 +393,7 @@ var AuthMagicLinkAddon = class extends require_hub_browser_origin.BaseAddon {
294
393
  reply.code(500);
295
394
  reply.send({
296
395
  error: "Magic-link request failed",
297
- message: require_hub_browser_origin.errMsg(err)
396
+ message: require_dist.errMsg(err)
298
397
  });
299
398
  }
300
399
  }
@@ -1,4 +1,39 @@
1
- import { a as buildAddonRouteProvider, c as errMsg, i as authProviderCapability, l as BaseAddon, n as addonRoutesCapability, o as loginMethodCapability, t as hubBrowserOrigin } from "../hub-browser-origin-B1o5vmQX.mjs";
1
+ import { a as isDisplayIconKey, c as errMsg, i as buildAddonRouteProvider, l as BaseAddon, o as loginMethodCapability, r as authProviderCapability, t as addonRoutesCapability } from "../dist-DbaYwR-N.mjs";
2
+ import { t as hubBrowserOrigin } from "../hub-browser-origin-LBBO5r05.mjs";
3
+ //#region src/magic-link/retire-enabled-flag.ts
4
+ /** The retired settings key. */
5
+ var RETIRED_ENABLED_KEY = "enabled";
6
+ /** Store key marking the retirement as done. Not a schema field. */
7
+ var RETIREMENT_MARKER_KEY = "enabledFlagRetired";
8
+ function deferred(reason) {
9
+ return {
10
+ kind: "deferred",
11
+ reason
12
+ };
13
+ }
14
+ function isMarked(read) {
15
+ return read.kind === "present" && read.value === true;
16
+ }
17
+ async function retireEnabledFlag(deps) {
18
+ const marker = await deps.readStoredKey(RETIREMENT_MARKER_KEY);
19
+ if (marker.kind === "unreadable") return deferred("settings store unreadable (marker)");
20
+ if (isMarked(marker)) return { kind: "already-retired" };
21
+ const flag = await deps.readStoredKey(RETIRED_ENABLED_KEY);
22
+ if (flag.kind === "unreadable") return deferred("settings store unreadable (enabled)");
23
+ const wasOn = flag.kind === "present" && flag.value === true;
24
+ if (!wasOn) try {
25
+ await deps.disableAtCapability();
26
+ } catch (err) {
27
+ return deferred(`capability toggle refused: ${err instanceof Error ? err.message : String(err)}`);
28
+ }
29
+ await deps.commit();
30
+ if (!isMarked(await deps.readStoredKey("enabledFlagRetired"))) return deferred("retirement write did not land");
31
+ return {
32
+ kind: "retired",
33
+ capDisabled: !wasOn
34
+ };
35
+ }
36
+ //#endregion
2
37
  //#region src/magic-link/auth-magic-link.addon.ts
3
38
  /**
4
39
  * Magic-link authentication addon.
@@ -13,10 +48,10 @@ import { a as buildAddonRouteProvider, c as errMsg, i as authProviderCapability,
13
48
  * 3. Email click hits `/login?bridge=…` → 302 to `/api/auth/sso/finish?bridge=…`
14
49
  * → hub verifies the bridge token and mints the real session.
15
50
  *
16
- * Delivery is pluggable. This addon ships a "log-only" mode by default
17
- * (writes the URL to the addon log so operators can copy it during
18
- * setup before SMTP is configured). Production deployments configure
19
- * the SMTP relay via global settings.
51
+ * Delivery is pluggable. This addon ships a "log-only" mode by default,
52
+ * which records that a link was requested but never writes the link
53
+ * itself (it is a login credential — D783). Production deployments
54
+ * configure the SMTP relay via global settings.
20
55
  *
21
56
  * Open follow-ups:
22
57
  * - Wire `nodemailer` for real SMTP delivery (peer dep; the framework
@@ -24,16 +59,30 @@ import { a as buildAddonRouteProvider, c as errMsg, i as authProviderCapability,
24
59
  * - Throttle requests per-email (rate-limit window).
25
60
  * - Auto-provision unknown emails (currently rejected for safety).
26
61
  */
62
+ /** Capability whose per-provider toggle is magic-link's ONE enable (Identity → Authentication). */
63
+ var GOVERNING_CAPABILITY = "auth-provider";
64
+ /** Backoff between retirement attempts — the first ones are refused until the
65
+ * provider registration reaches the hub. After the last, the next boot retries. */
66
+ var RETIREMENT_RETRY_DELAYS_MS = [
67
+ 1e3,
68
+ 2e3,
69
+ 5e3,
70
+ 1e4,
71
+ 3e4,
72
+ 6e4
73
+ ];
27
74
  var DEFAULT_CONFIG = {
28
- enabled: false,
29
75
  displayName: "Email magic link",
30
- icon: "mail",
76
+ icon: "mailbox",
31
77
  publicOrigin: "",
32
78
  deliveryMode: "log-only",
33
79
  defaultRole: "viewer",
34
80
  ttlSec: 600
35
81
  };
36
82
  var AuthMagicLinkAddon = class extends BaseAddon {
83
+ enabledFlagRetired = false;
84
+ retirementTimer = null;
85
+ shuttingDown = false;
37
86
  constructor() {
38
87
  super({ ...DEFAULT_CONFIG });
39
88
  }
@@ -73,6 +122,8 @@ var AuthMagicLinkAddon = class extends BaseAddon {
73
122
  ]);
74
123
  const loginMethodProvider = { getLoginMethods: async () => this.buildLoginMethods() };
75
124
  this.ctx.logger.info("Magic-link auth provider initialized", { meta: { deliveryMode: this.config.deliveryMode } });
125
+ this.shuttingDown = false;
126
+ this.attemptEnabledFlagRetirement(0);
76
127
  return [
77
128
  {
78
129
  capability: authProviderCapability,
@@ -89,16 +140,70 @@ var AuthMagicLinkAddon = class extends BaseAddon {
89
140
  ];
90
141
  }
91
142
  buildLoginMethods() {
92
- if (!this.config.enabled) return [];
143
+ if (!this.enabledFlagRetired) return [];
93
144
  return [{
94
145
  kind: "redirect",
95
146
  id: "auth-magic-link",
96
147
  label: this.config.displayName || DEFAULT_CONFIG.displayName,
97
- icon: this.config.icon || DEFAULT_CONFIG.icon,
148
+ icon: isDisplayIconKey(this.config.icon) ? this.config.icon : DEFAULT_CONFIG.icon,
98
149
  startUrl: "/addon/auth-magic-link/start",
99
150
  stage: "primary"
100
151
  }];
101
152
  }
153
+ async onShutdown() {
154
+ this.shuttingDown = true;
155
+ if (this.retirementTimer !== null) clearTimeout(this.retirementTimer);
156
+ this.retirementTimer = null;
157
+ }
158
+ /** One attempt of the D777 carry-over; reschedules itself while deferred. */
159
+ async attemptEnabledFlagRetirement(attempt) {
160
+ if (this.shuttingDown) return;
161
+ let outcome;
162
+ try {
163
+ outcome = await retireEnabledFlag({
164
+ readStoredKey: (key) => this.readStoredGlobalKey(key),
165
+ disableAtCapability: async () => {
166
+ await this.ctx.api.addons.setCapabilityProviderEnabled.mutate({
167
+ capName: GOVERNING_CAPABILITY,
168
+ addonId: this.ctx.id,
169
+ enabled: false
170
+ });
171
+ },
172
+ commit: async () => {
173
+ const patch = {
174
+ [RETIRED_ENABLED_KEY]: void 0,
175
+ [RETIREMENT_MARKER_KEY]: true
176
+ };
177
+ await this.updateGlobalSettings(patch);
178
+ }
179
+ });
180
+ } catch (err) {
181
+ outcome = {
182
+ kind: "deferred",
183
+ reason: errMsg(err)
184
+ };
185
+ }
186
+ if (outcome.kind !== "deferred") {
187
+ this.enabledFlagRetired = true;
188
+ if (outcome.kind === "retired") this.ctx.logger.info(outcome.capDisabled ? "Magic-link: the retired `enabled` setting was off — the provider is now DISABLED on Identity → Authentication; enable it there to offer magic-link login." : "Magic-link: the retired `enabled` setting was on — the provider stays enabled on Identity → Authentication.");
189
+ return;
190
+ }
191
+ const delay = RETIREMENT_RETRY_DELAYS_MS[attempt];
192
+ if (delay === void 0) {
193
+ this.ctx.logger.warn("Magic-link: carrying the retired `enabled` setting over to the capability toggle failed — login button withheld until the next boot", { meta: { reason: outcome.reason } });
194
+ return;
195
+ }
196
+ this.ctx.logger.debug("Magic-link: enabled-flag retirement deferred", { meta: {
197
+ reason: outcome.reason,
198
+ attempt,
199
+ retryInMs: delay
200
+ } });
201
+ this.retirementTimer = setTimeout(() => {
202
+ this.retirementTimer = null;
203
+ this.attemptEnabledFlagRetirement(attempt + 1);
204
+ }, delay);
205
+ this.retirementTimer.unref?.();
206
+ }
102
207
  globalSettingsSchema() {
103
208
  return this.schema({ sections: [{
104
209
  id: "magic-link",
@@ -106,13 +211,6 @@ var AuthMagicLinkAddon = class extends BaseAddon {
106
211
  description: "Passwordless email login. The user types an email; the hub mails them a one-time link valid for the configured TTL.",
107
212
  columns: 1,
108
213
  fields: [
109
- this.field({
110
- type: "boolean",
111
- key: "enabled",
112
- label: "Enabled",
113
- description: "Offer the magic-link button on the login page. Leave off until email delivery is configured.",
114
- default: DEFAULT_CONFIG.enabled
115
- }),
116
214
  this.field({
117
215
  type: "text",
118
216
  key: "displayName",
@@ -120,9 +218,10 @@ var AuthMagicLinkAddon = class extends BaseAddon {
120
218
  default: DEFAULT_CONFIG.displayName
121
219
  }),
122
220
  this.field({
123
- type: "text",
221
+ type: "icon",
124
222
  key: "icon",
125
223
  label: "Icon",
224
+ description: "Shown on the login button.",
126
225
  default: DEFAULT_CONFIG.icon
127
226
  }),
128
227
  this.field({
@@ -136,7 +235,7 @@ var AuthMagicLinkAddon = class extends BaseAddon {
136
235
  type: "select",
137
236
  key: "deliveryMode",
138
237
  label: "Delivery mode",
139
- description: "log-only writes the URL to the addon log (setup only). smtp uses the configured SMTP relay.",
238
+ description: "log-only records the request in the addon log but never the link (it is a login credential). smtp uses the configured SMTP relay.",
140
239
  default: DEFAULT_CONFIG.deliveryMode,
141
240
  options: [{
142
241
  value: "log-only",
@@ -223,7 +322,7 @@ var AuthMagicLinkAddon = class extends BaseAddon {
223
322
  <input id="email" name="email" type="email" autocomplete="email" required autofocus placeholder="you@example.com">
224
323
  <button id="b" type="submit">Send login link</button>
225
324
  <div class="msg" id="m" hidden></div>
226
- <p style="margin-top:1rem"><a href="/admin/login">Back to sign in</a></p>
325
+ <p style="margin-top:1rem"><a href="/login">Back to sign in</a></p>
227
326
  </form>
228
327
  <script>
229
328
  var f=document.getElementById('f'),b=document.getElementById('b'),m=document.getElementById('m');
@@ -273,15 +372,15 @@ var AuthMagicLinkAddon = class extends BaseAddon {
273
372
  reply.send({ error: "sso-bridge capability unavailable" });
274
373
  return;
275
374
  }
276
- const link = `${await this.buildPublicOrigin()}/addon/auth-magic-link/login?bridge=${encodeURIComponent(token)}`;
277
- if (this.config.deliveryMode === "log-only") this.ctx.logger.warn("Magic link generated (log-only mode — NOT delivered via email)", { meta: {
375
+ `${await this.buildPublicOrigin()}${encodeURIComponent(token)}`;
376
+ if (this.config.deliveryMode === "log-only") this.ctx.logger.warn("Magic link generated — not delivered (log-only mode, link withheld)", { meta: {
278
377
  email,
279
- link,
280
- ttlSec: this.config.ttlSec
378
+ ttlSec: this.config.ttlSec,
379
+ delivered: false
281
380
  } });
282
- else this.ctx.logger.warn("Magic link delivery: SMTP mode requested but no smtp-provider cap registered. Falling back to log.", { meta: {
381
+ else this.ctx.logger.warn("Magic link not delivered: SMTP mode requested but no smtp-provider cap registered", { meta: {
283
382
  email,
284
- link
383
+ delivered: false
285
384
  } });
286
385
  reply.code(200);
287
386
  reply.send(ok);