@forgezero/runtime 0.1.19 → 0.1.21

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.
@@ -88,27 +88,36 @@ function createNotifier(options) {
88
88
  }
89
89
  function layout(args) {
90
90
  const brand = escapeHtml(args.brand ?? "ForgeZero");
91
+ const preheader = escapeHtml(args.preheader ?? `${args.title} — secure account notification`);
91
92
  return `<!doctype html>
92
- <html><body style="margin:0;padding:0;background:#0b0d10;color:#e6e8eb;font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif">
93
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#0b0d10;padding:32px 16px">
93
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark"><meta name="supported-color-schemes" content="dark"></head>
94
+ <body style="margin:0;padding:0;background:#090b0e;color:#f4f5f7;font-family:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;-webkit-text-size-adjust:100%">
95
+ <div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent;line-height:1px;font-size:1px">${preheader}&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;</div>
96
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;background:#090b0e;padding:48px 16px">
94
97
  <tr><td align="center">
95
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:520px;background:#14171c;border:1px solid #232830;border-radius:12px">
96
- <tr><td style="padding:28px 28px 8px">
97
- <div style="font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#8b93a1">${brand}</div>
98
- <h1 style="margin:12px 0 0;font-size:20px;line-height:1.3;font-weight:600;color:#f2f4f7">${args.title}</h1>
98
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:580px;border-collapse:separate;background:#15181d;border:1px solid #30353d;border-radius:18px;box-shadow:0 24px 72px rgba(0,0,0,.42);overflow:hidden">
99
+ <tr><td style="height:3px;background:linear-gradient(90deg,#ff7a1a 0%,#ffb25c 38%,#30353d 38%);border-radius:18px 18px 0 0;font-size:0;line-height:0">&nbsp;</td></tr>
100
+ <tr><td style="padding:28px 32px 12px">
101
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
102
+ <td valign="middle"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td style="width:34px;height:34px;border:1px solid #5b3822;border-radius:10px;background:#20150e;text-align:center;color:#ff8b3d;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:16px;font-weight:800">FZ</td><td style="padding-left:11px;font-size:13px;font-weight:750;letter-spacing:.11em;text-transform:uppercase;color:#f4f5f7">${brand}</td></tr></table></td>
103
+ <td align="right" valign="middle"><span style="display:inline-block;padding:5px 8px;border:1px solid #394049;border-radius:999px;color:#9da5b2;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase">Protected message</span></td>
104
+ </tr></table>
105
+ <div style="margin-top:30px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:#ff8b3d">Account security</div>
106
+ <h1 style="margin:9px 0 0;font-size:26px;line-height:1.22;font-weight:700;letter-spacing:-.025em;color:#f7f8fa">${args.title}</h1>
99
107
  </td></tr>
100
- <tr><td style="padding:12px 28px 28px;font-size:15px;line-height:1.6;color:#c3c9d4">${args.body}</td></tr>
108
+ <tr><td style="padding:14px 32px 30px;font-size:15px;line-height:1.65;color:#c8cdd6">${args.body}</td></tr>
109
+ <tr><td style="padding:15px 32px;border-top:1px solid #2a2f36;background:#111419;color:#7f8793;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;line-height:1.5;letter-spacing:.06em;text-transform:uppercase">Encrypted transport &nbsp;·&nbsp; Request-bound verification &nbsp;·&nbsp; No password</td></tr>
101
110
  </table>
102
- ${args.footer ? `<div style="max-width:520px;margin:16px auto 0;font-size:12px;line-height:1.5;color:#6b7280">${args.footer}</div>` : ""}
111
+ ${args.footer ? `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:580px"><tr><td style="padding:20px 18px 0;text-align:center;font-size:12px;line-height:1.6;color:#747c89">${args.footer}<br><span style="display:inline-block;margin-top:5px;color:#505762">ForgeZero · secure infrastructure control plane</span></td></tr></table>` : ""}
103
112
  </td></tr></table>
104
113
  </body></html>`;
105
114
  }
106
- var codeBlock = (code) => `<div style="margin:20px 0;padding:16px;background:#0b0d10;border:1px solid #232830;border-radius:8px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:28px;letter-spacing:.32em;color:#f2f4f7">${code}</div>`;
107
- var button = (label, href) => `<div style="margin:24px 0"><a href="${href}" style="display:inline-block;padding:12px 20px;background:#e6e8eb;color:#0b0d10;border-radius:8px;font-weight:600;font-size:15px;text-decoration:none">${label}</a></div>`;
115
+ var codeBlock = (code) => `<div style="margin:24px 0;padding:22px 16px;background:#0b0d10;border:1px solid #3b414b;border-radius:12px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:32px;line-height:1;font-weight:750;letter-spacing:.32em;color:#ffffff;box-shadow:inset 3px 0 0 #ff7a1a,0 10px 28px rgba(0,0,0,.22)">${code}</div>`;
116
+ var button = (label, href) => `<div style="margin:26px 0"><a href="${href}" style="display:inline-block;padding:13px 21px;background:#ff7a1a;border:1px solid #ff9b52;color:#160b04;border-radius:10px;font-weight:750;font-size:15px;line-height:1;text-decoration:none;box-shadow:0 10px 24px rgba(255,122,26,.2)">${label}&nbsp;&nbsp;→</a></div>`;
108
117
  var VERSION = "0.1.0";
109
118
 
110
119
  // src/notify-templates.ts
111
- var FOOTER = "ForgeZero sent this because somebody asked to. If it was not you, nothing has changed yet but tell us.";
120
+ var FOOTER = "You received this transactional security message because an action was requested for your account. If it was not you, do not share any code or approve the request.";
112
121
  var signInCode = defineTemplate({
113
122
  key: "auth.sign-in-code",
114
123
  channel: "email",
@@ -116,12 +125,14 @@ var signInCode = defineTemplate({
116
125
  data: ["code", "minutes"],
117
126
  sample: { code: "418302", minutes: "10" },
118
127
  subject: (data) => `${data.code} is your ForgeZero sign-in code`,
119
- text: (data) => `${data.code} is your sign-in code. It expires in ${data.minutes} minutes.
128
+ text: (data) => `${data.code} is your ForgeZero verification code. It expires in ${data.minutes} minutes.
120
129
 
121
- If you did not try to sign in, someone has your email address and is trying to use it. The code alone will not let them in.`,
130
+ Enter it only in the ForgeZero window where you requested it. ForgeZero support will never ask for this code.
131
+
132
+ If you did not request it, ignore this email and review your active sessions.`,
122
133
  html: (data) => layout({
123
134
  title: "Your sign-in code",
124
- body: codeBlock(data.code) + `<p style="margin:0">Expires in ${data.minutes} minutes.</p>` + `<p style="margin:16px 0 0;color:#8b93a1">If you did not try to sign in, someone has your email address and is trying to use it. The code alone will not let them in.</p>`,
135
+ body: codeBlock(data.code) + `<p style="margin:0;color:#f4f5f7">Enter this code only in the ForgeZero window where you requested it.</p>` + `<p style="margin:8px 0 0;color:#929aa7">It expires in ${data.minutes} minutes. ForgeZero support will never ask you to share it.</p>` + `<div style="margin:20px 0 0;padding:12px 14px;border:1px solid #3b414b;border-radius:9px;background:#101318;color:#929aa7;font-size:13px;line-height:1.55">Did not request this? Ignore the code and review your active sessions. No account change is completed by this email alone.</div>`,
125
136
  footer: FOOTER
126
137
  })
127
138
  });
package/dist/notify.d.ts CHANGED
@@ -165,6 +165,7 @@ export declare function layout(args: {
165
165
  body: string;
166
166
  footer?: string;
167
167
  brand?: string;
168
+ preheader?: string;
168
169
  }): string;
169
170
  /** A code shown as digits a human can read aloud without losing their place. */
170
171
  export declare const codeBlock: (code: string) => string;
package/dist/notify.js CHANGED
@@ -88,23 +88,32 @@ function createNotifier(options) {
88
88
  }
89
89
  function layout(args) {
90
90
  const brand = escapeHtml(args.brand ?? "ForgeZero");
91
+ const preheader = escapeHtml(args.preheader ?? `${args.title} — secure account notification`);
91
92
  return `<!doctype html>
92
- <html><body style="margin:0;padding:0;background:#0b0d10;color:#e6e8eb;font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif">
93
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#0b0d10;padding:32px 16px">
93
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark"><meta name="supported-color-schemes" content="dark"></head>
94
+ <body style="margin:0;padding:0;background:#090b0e;color:#f4f5f7;font-family:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;-webkit-text-size-adjust:100%">
95
+ <div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent;line-height:1px;font-size:1px">${preheader}&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;</div>
96
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="width:100%;background:#090b0e;padding:48px 16px">
94
97
  <tr><td align="center">
95
- <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:520px;background:#14171c;border:1px solid #232830;border-radius:12px">
96
- <tr><td style="padding:28px 28px 8px">
97
- <div style="font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#8b93a1">${brand}</div>
98
- <h1 style="margin:12px 0 0;font-size:20px;line-height:1.3;font-weight:600;color:#f2f4f7">${args.title}</h1>
98
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:580px;border-collapse:separate;background:#15181d;border:1px solid #30353d;border-radius:18px;box-shadow:0 24px 72px rgba(0,0,0,.42);overflow:hidden">
99
+ <tr><td style="height:3px;background:linear-gradient(90deg,#ff7a1a 0%,#ffb25c 38%,#30353d 38%);border-radius:18px 18px 0 0;font-size:0;line-height:0">&nbsp;</td></tr>
100
+ <tr><td style="padding:28px 32px 12px">
101
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
102
+ <td valign="middle"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td style="width:34px;height:34px;border:1px solid #5b3822;border-radius:10px;background:#20150e;text-align:center;color:#ff8b3d;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:16px;font-weight:800">FZ</td><td style="padding-left:11px;font-size:13px;font-weight:750;letter-spacing:.11em;text-transform:uppercase;color:#f4f5f7">${brand}</td></tr></table></td>
103
+ <td align="right" valign="middle"><span style="display:inline-block;padding:5px 8px;border:1px solid #394049;border-radius:999px;color:#9da5b2;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase">Protected message</span></td>
104
+ </tr></table>
105
+ <div style="margin-top:30px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:#ff8b3d">Account security</div>
106
+ <h1 style="margin:9px 0 0;font-size:26px;line-height:1.22;font-weight:700;letter-spacing:-.025em;color:#f7f8fa">${args.title}</h1>
99
107
  </td></tr>
100
- <tr><td style="padding:12px 28px 28px;font-size:15px;line-height:1.6;color:#c3c9d4">${args.body}</td></tr>
108
+ <tr><td style="padding:14px 32px 30px;font-size:15px;line-height:1.65;color:#c8cdd6">${args.body}</td></tr>
109
+ <tr><td style="padding:15px 32px;border-top:1px solid #2a2f36;background:#111419;color:#7f8793;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;line-height:1.5;letter-spacing:.06em;text-transform:uppercase">Encrypted transport &nbsp;·&nbsp; Request-bound verification &nbsp;·&nbsp; No password</td></tr>
101
110
  </table>
102
- ${args.footer ? `<div style="max-width:520px;margin:16px auto 0;font-size:12px;line-height:1.5;color:#6b7280">${args.footer}</div>` : ""}
111
+ ${args.footer ? `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:580px"><tr><td style="padding:20px 18px 0;text-align:center;font-size:12px;line-height:1.6;color:#747c89">${args.footer}<br><span style="display:inline-block;margin-top:5px;color:#505762">ForgeZero · secure infrastructure control plane</span></td></tr></table>` : ""}
103
112
  </td></tr></table>
104
113
  </body></html>`;
105
114
  }
106
- var codeBlock = (code) => `<div style="margin:20px 0;padding:16px;background:#0b0d10;border:1px solid #232830;border-radius:8px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:28px;letter-spacing:.32em;color:#f2f4f7">${code}</div>`;
107
- var button = (label, href) => `<div style="margin:24px 0"><a href="${href}" style="display:inline-block;padding:12px 20px;background:#e6e8eb;color:#0b0d10;border-radius:8px;font-weight:600;font-size:15px;text-decoration:none">${label}</a></div>`;
115
+ var codeBlock = (code) => `<div style="margin:24px 0;padding:22px 16px;background:#0b0d10;border:1px solid #3b414b;border-radius:12px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:32px;line-height:1;font-weight:750;letter-spacing:.32em;color:#ffffff;box-shadow:inset 3px 0 0 #ff7a1a,0 10px 28px rgba(0,0,0,.22)">${code}</div>`;
116
+ var button = (label, href) => `<div style="margin:26px 0"><a href="${href}" style="display:inline-block;padding:13px 21px;background:#ff7a1a;border:1px solid #ff9b52;color:#160b04;border-radius:10px;font-weight:750;font-size:15px;line-height:1;text-decoration:none;box-shadow:0 10px 24px rgba(255,122,26,.2)">${label}&nbsp;&nbsp;→</a></div>`;
108
117
  var VERSION = "0.1.0";
109
118
  export {
110
119
  render,
@@ -141,6 +141,7 @@ function describeJsonSchema(schema, prefix = "") {
141
141
  required: required.has(name),
142
142
  description: property.description,
143
143
  writeOnly: property.writeOnly === true ? true : undefined,
144
+ readOnly: property.readOnly === true ? true : undefined,
144
145
  default: property.default,
145
146
  format: property.format,
146
147
  minLength: property.minLength,
@@ -181,6 +182,7 @@ function describeProperty(property, path, label, required) {
181
182
  required,
182
183
  description: property.description,
183
184
  writeOnly: property.writeOnly === true ? true : undefined,
185
+ readOnly: property.readOnly === true ? true : undefined,
184
186
  default: property.default,
185
187
  format: property.format,
186
188
  minLength: property.minLength,
package/dist/schema.d.ts CHANGED
@@ -61,6 +61,8 @@ export interface FormField {
61
61
  * is what routes a credential into the vault instead of onto a page.
62
62
  */
63
63
  writeOnly?: boolean;
64
+ /** Stored and validated, but supplied only by a trusted backend resolver. */
65
+ readOnly?: boolean;
64
66
  default?: unknown;
65
67
  /** `enum` only. */
66
68
  options?: readonly string[];
package/dist/schema.js CHANGED
@@ -141,6 +141,7 @@ function describeJsonSchema(schema, prefix = "") {
141
141
  required: required.has(name),
142
142
  description: property.description,
143
143
  writeOnly: property.writeOnly === true ? true : undefined,
144
+ readOnly: property.readOnly === true ? true : undefined,
144
145
  default: property.default,
145
146
  format: property.format,
146
147
  minLength: property.minLength,
@@ -181,6 +182,7 @@ function describeProperty(property, path, label, required) {
181
182
  required,
182
183
  description: property.description,
183
184
  writeOnly: property.writeOnly === true ? true : undefined,
185
+ readOnly: property.readOnly === true ? true : undefined,
184
186
  default: property.default,
185
187
  format: property.format,
186
188
  minLength: property.minLength,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/runtime",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -138,7 +138,7 @@
138
138
  "prepublishOnly": "bun ../tools/package-task.ts prepublish runtime"
139
139
  },
140
140
  "dependencies": {
141
- "@forgezero/access": "^0.1.13"
141
+ "@forgezero/access": "^0.1.14"
142
142
  },
143
143
  "peerDependencies": {
144
144
  "@noble/ciphers": "^2.2.0",