@draftlab/auth 0.13.0 → 0.14.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.
@@ -145,6 +145,11 @@ const CodeProvider = (config) => {
145
145
  ...currentState,
146
146
  resend: false
147
147
  }, formData, { type: "invalid_code" });
148
+ if (!await ctx.get(c, "authorization")) return transition(c, { type: "start" }, formData, {
149
+ type: "invalid_claim",
150
+ key: "session",
151
+ value: "Authentication session expired"
152
+ });
148
153
  await ctx.unset(c, "provider");
149
154
  return await ctx.success(c, { claims: currentState.claims });
150
155
  }
@@ -74,6 +74,7 @@ const MagicLinkProvider = (config) => {
74
74
  if (!urlValue || !storedValue) return false;
75
75
  return timingSafeCompare(storedValue, urlValue);
76
76
  })) return transition(c, { type: "start" }, void 0, { type: "invalid_link" });
77
+ if (!await ctx.get(c, "authorization")) return transition(c, { type: "start" }, void 0, { type: "invalid_link" });
77
78
  await ctx.unset(c, "provider");
78
79
  return await ctx.success(c, { claims: storedState.claims });
79
80
  });
@@ -171,7 +171,7 @@ const PasskeyProvider = (config) => {
171
171
  authenticatorSelection: authenticatorSelection ?? {
172
172
  residentKey: "preferred",
173
173
  userVerification: "preferred",
174
- authenticatorAttachment: otherDevice ? "cross-platform" : "platform"
174
+ authenticatorAttachment: otherDevice ? void 0 : "platform"
175
175
  },
176
176
  timeout
177
177
  });
@@ -193,6 +193,14 @@ const PasswordProvider = (config) => {
193
193
  type: "start",
194
194
  redirect: provider.redirect
195
195
  }, { type: "invalid_email" });
196
+ if (!await Storage.get(ctx.storage, [
197
+ "email",
198
+ email,
199
+ "password"
200
+ ])) return transition({
201
+ type: "start",
202
+ redirect: provider.redirect
203
+ }, { type: "invalid_email" });
196
204
  const code = generateCode();
197
205
  const context = provider.type === "code" && provider.email === email ? "reset:resend" : "reset";
198
206
  await config.sendCode(email, code, context);
@@ -221,6 +229,7 @@ const PasswordProvider = (config) => {
221
229
  const password = formData.get("password")?.toString();
222
230
  const repeat = formData.get("repeat")?.toString();
223
231
  if (!password) return transition(provider, { type: "invalid_password" });
232
+ if (!repeat) return transition(provider, { type: "invalid_password" });
224
233
  if (password !== repeat) return transition(provider, { type: "password_mismatch" });
225
234
  if (config.validatePassword) {
226
235
  let validationError;
package/dist/ui/code.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { run } from "../util.mjs";
1
2
  import { Layout, renderToHTML } from "./base.mjs";
2
3
  import { FormAlert } from "./form.mjs";
3
4
  import { jsx, jsxs } from "preact/jsx-runtime";
@@ -57,10 +58,13 @@ const CodeUI = (options) => {
57
58
  "data-component": "form",
58
59
  method: "post",
59
60
  children: [
60
- success ? /* @__PURE__ */ jsx(FormAlert, {
61
- message: success.message,
62
- color: "success"
63
- }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
61
+ run(() => {
62
+ if (success) return /* @__PURE__ */ jsx(FormAlert, {
63
+ message: success.message,
64
+ color: "success"
65
+ });
66
+ return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
67
+ }),
64
68
  /* @__PURE__ */ jsx("input", {
65
69
  "data-component": "input",
66
70
  type: mode === "email" ? "email" : "tel",
@@ -97,10 +101,13 @@ const CodeUI = (options) => {
97
101
  "data-component": "form",
98
102
  method: "post",
99
103
  children: [
100
- success ? /* @__PURE__ */ jsx(FormAlert, {
101
- message: success.message,
102
- color: "success"
103
- }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
104
+ run(() => {
105
+ if (success) return /* @__PURE__ */ jsx(FormAlert, {
106
+ message: success.message,
107
+ color: "success"
108
+ });
109
+ return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
110
+ }),
104
111
  /* @__PURE__ */ jsx("input", {
105
112
  name: "action",
106
113
  type: "hidden",
@@ -1,3 +1,4 @@
1
+ import { run } from "../util.mjs";
1
2
  import { Layout, renderToHTML } from "./base.mjs";
2
3
  import { FormAlert } from "./form.mjs";
3
4
  import { jsx, jsxs } from "preact/jsx-runtime";
@@ -55,10 +56,13 @@ const MagicLinkUI = (options) => {
55
56
  "data-component": "form",
56
57
  method: "post",
57
58
  children: [
58
- success ? /* @__PURE__ */ jsx(FormAlert, {
59
- message: success.message,
60
- color: "success"
61
- }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
59
+ run(() => {
60
+ if (success) return /* @__PURE__ */ jsx(FormAlert, {
61
+ message: success.message,
62
+ color: "success"
63
+ });
64
+ return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
65
+ }),
62
66
  /* @__PURE__ */ jsx("input", {
63
67
  "data-component": "input",
64
68
  type: mode === "email" ? "email" : "tel",
@@ -99,10 +103,13 @@ const MagicLinkUI = (options) => {
99
103
  "data-component": "title",
100
104
  children: "Check your email"
101
105
  }),
102
- success ? /* @__PURE__ */ jsx(FormAlert, {
103
- message: success.message,
104
- color: "success"
105
- }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
106
+ run(() => {
107
+ if (success) return /* @__PURE__ */ jsx(FormAlert, {
108
+ message: success.message,
109
+ color: "success"
110
+ });
111
+ return /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) });
112
+ }),
106
113
  /* @__PURE__ */ jsx("p", {
107
114
  "data-component": "description",
108
115
  children: "Click the link in your email to sign in."
@@ -12,10 +12,12 @@ interface PasskeyUICopy {
12
12
  readonly register_prompt: string;
13
13
  readonly login_prompt: string;
14
14
  readonly login: string;
15
- readonly change_prompt: string;
16
- readonly code_resend: string;
17
- readonly code_return: string;
18
15
  readonly input_email: string;
16
+ readonly error_register_already_registered: string;
17
+ readonly error_register_cancelled: string;
18
+ readonly error_register_failed: string;
19
+ readonly error_auth_cancelled: string;
20
+ readonly error_auth_failed: string;
19
21
  }
20
22
  interface PasskeyUIOptions extends Omit<PasskeyProviderConfig, "authorize" | "register" | "copy"> {
21
23
  readonly copy?: Partial<PasskeyUICopy>;
@@ -10,10 +10,12 @@ const DEFAULT_COPY = {
10
10
  login_prompt: "Already have an account?",
11
11
  login: "Login",
12
12
  button_continue: "Continue",
13
- change_prompt: "Forgot password?",
14
- code_resend: "Resend code",
15
- code_return: "Back to",
16
- input_email: "Email"
13
+ input_email: "Email",
14
+ error_register_already_registered: "This device is already registered. Please use the login page or try a different device.",
15
+ error_register_cancelled: "Registration was cancelled or timed out. Please try again.",
16
+ error_register_failed: "Registration failed. Please try again.",
17
+ error_auth_cancelled: "Authentication was cancelled or timed out. Please try again.",
18
+ error_auth_failed: "Authentication failed. Please try again."
17
19
  };
18
20
  const PasskeyUI = (options) => {
19
21
  const { rpName, rpID, origin, userCanRegisterPasskey, authenticatorSelection, attestationType, timeout } = options;
@@ -57,8 +59,17 @@ const PasskeyUI = (options) => {
57
59
  // Pass the options to the authenticator and wait for a response
58
60
  attResp = await startAuthentication({ optionsJSON });
59
61
  } catch (error) {
60
- message.textContent = error;
61
- throw error;
62
+ // Handle WebAuthn errors with friendly messages
63
+ const errorName = error.name;
64
+ if (errorName === "NotAllowedError") {
65
+ message.textContent = "${copy.error_auth_cancelled}";
66
+ } else if (errorName === "InvalidStateError") {
67
+ message.textContent = "${copy.error_auth_failed}";
68
+ } else {
69
+ message.textContent = error.message || "${copy.error_auth_failed}";
70
+ }
71
+ console.error(error);
72
+ return;
62
73
  }
63
74
 
64
75
  const verificationResp = await fetch(
@@ -147,6 +158,7 @@ const PasskeyUI = (options) => {
147
158
  window.addEventListener("load", async () => {
148
159
  const { startRegistration } = SimpleWebAuthnBrowser;
149
160
  const registerForm = document.getElementById("registerForm");
161
+ const btnOtherDevice = document.getElementById("btnOtherDevice");
150
162
  const message = document.querySelector("[data-slot='message']");
151
163
  const origin = window.location.origin;
152
164
  const rpID = window.location.hostname;
@@ -181,8 +193,17 @@ const PasskeyUI = (options) => {
181
193
  // Pass the options to the authenticator and wait for a response
182
194
  attResp = await startRegistration({ optionsJSON });
183
195
  } catch (error) {
184
- message.textContent = error;
185
- throw error;
196
+ // Handle WebAuthn errors with friendly messages
197
+ const errorName = error.name;
198
+ if (errorName === "InvalidStateError") {
199
+ message.textContent = "${copy.error_register_already_registered}";
200
+ } else if (errorName === "NotAllowedError") {
201
+ message.textContent = "${copy.error_register_cancelled}";
202
+ } else {
203
+ message.textContent = error.message || "${copy.error_register_failed}";
204
+ }
205
+ console.error(error);
206
+ return;
186
207
  }
187
208
 
188
209
  // POST the response to the endpoint that calls
@@ -232,6 +253,11 @@ const PasskeyUI = (options) => {
232
253
  e.preventDefault();
233
254
  register();
234
255
  });
256
+
257
+ btnOtherDevice.addEventListener("click", (e) => {
258
+ e.preventDefault();
259
+ register(true);
260
+ });
235
261
  });
236
262
  ` } }),
237
263
  /* @__PURE__ */ jsxs("form", {
@@ -11,11 +11,6 @@ interface PasswordUICopy {
11
11
  readonly error_invalid_email: string;
12
12
  readonly error_invalid_password: string;
13
13
  readonly error_password_mismatch: string;
14
- readonly error_validation_error: string;
15
- readonly register_title: string;
16
- readonly register_description: string;
17
- readonly login_title: string;
18
- readonly login_description: string;
19
14
  readonly register: string;
20
15
  readonly register_prompt: string;
21
16
  readonly login_prompt: string;
@@ -1,3 +1,4 @@
1
+ import { run } from "../util.mjs";
1
2
  import { Layout, renderToHTML } from "./base.mjs";
2
3
  import { FormAlert } from "./form.mjs";
3
4
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
@@ -12,11 +13,6 @@ const DEFAULT_COPY = {
12
13
  error_invalid_email: "Email is not valid.",
13
14
  error_invalid_password: "Password is incorrect.",
14
15
  error_password_mismatch: "Passwords do not match.",
15
- error_validation_error: "Password does not meet requirements.",
16
- register_title: "Welcome to the app",
17
- register_description: "Sign in with your email",
18
- login_title: "Welcome to the app",
19
- login_description: "Sign in with your email",
20
16
  register: "Register",
21
17
  register_prompt: "Don't have an account?",
22
18
  login_prompt: "Already have an account?",
@@ -105,130 +101,133 @@ const PasswordUI = (options) => {
105
101
  "password_mismatch",
106
102
  "validation_error"
107
103
  ].includes(error?.type || "");
108
- return /* @__PURE__ */ jsx(Layout, { children: state.type === "start" ? /* @__PURE__ */ jsxs("form", {
109
- "data-component": "form",
110
- method: "post",
111
- children: [
112
- /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
113
- /* @__PURE__ */ jsx("input", {
114
- name: "action",
115
- type: "hidden",
116
- value: "register"
117
- }),
118
- /* @__PURE__ */ jsx("input", {
119
- type: "email",
120
- name: "email",
121
- placeholder: copy.input_email,
122
- value: emailError ? "" : form?.get("email")?.toString() || "",
123
- autoComplete: "email",
124
- "data-component": "input",
125
- required: true
126
- }),
127
- /* @__PURE__ */ jsx("input", {
128
- type: "password",
129
- name: "password",
130
- placeholder: copy.input_password,
131
- value: passwordError ? "" : form?.get("password")?.toString() || "",
132
- autoComplete: "new-password",
133
- "data-component": "input",
134
- required: true
135
- }),
136
- /* @__PURE__ */ jsx("input", {
137
- type: "password",
138
- name: "repeat",
139
- placeholder: copy.input_repeat,
140
- autoComplete: "new-password",
141
- "data-component": "input",
142
- required: true
143
- }),
144
- /* @__PURE__ */ jsx("button", {
145
- "data-component": "button",
146
- type: "submit",
147
- children: copy.button_continue
148
- }),
149
- /* @__PURE__ */ jsx("div", {
150
- "data-component": "form-footer",
151
- children: /* @__PURE__ */ jsxs("span", { children: [
152
- copy.login_prompt,
153
- " ",
154
- /* @__PURE__ */ jsx("a", {
155
- "data-component": "link",
156
- href: "./authorize",
157
- children: copy.login
158
- })
159
- ] })
160
- })
161
- ]
162
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("form", {
163
- "data-component": "form",
164
- method: "post",
165
- children: [
166
- /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
167
- /* @__PURE__ */ jsx("input", {
168
- name: "action",
169
- type: "hidden",
170
- value: "verify"
171
- }),
172
- /* @__PURE__ */ jsx("input", {
173
- type: "text",
174
- name: "code",
175
- placeholder: copy.input_code,
176
- "aria-label": "6-digit verification code",
177
- autoComplete: "one-time-code",
178
- "data-component": "input",
179
- inputMode: "numeric",
180
- maxLength: 6,
181
- minLength: 6,
182
- pattern: "[0-9]{6}",
183
- required: true
184
- }),
185
- /* @__PURE__ */ jsx("button", {
186
- "data-component": "button",
187
- type: "submit",
188
- children: copy.button_continue
189
- })
190
- ]
191
- }), /* @__PURE__ */ jsxs("form", {
192
- method: "post",
193
- children: [
194
- /* @__PURE__ */ jsx("input", {
195
- name: "action",
196
- type: "hidden",
197
- value: "register"
198
- }),
199
- /* @__PURE__ */ jsx("input", {
200
- name: "email",
201
- type: "hidden",
202
- value: state.email
203
- }),
204
- /* @__PURE__ */ jsx("input", {
205
- name: "password",
206
- type: "hidden",
207
- value: ""
208
- }),
209
- /* @__PURE__ */ jsx("input", {
210
- name: "repeat",
211
- type: "hidden",
212
- value: ""
213
- }),
214
- /* @__PURE__ */ jsxs("div", {
215
- "data-component": "form-footer",
216
- children: [/* @__PURE__ */ jsxs("span", { children: [
217
- copy.code_return,
218
- " ",
219
- /* @__PURE__ */ jsx("a", {
220
- "data-component": "link",
221
- href: "./authorize",
222
- children: copy.login
223
- })
224
- ] }), /* @__PURE__ */ jsx("button", {
104
+ return /* @__PURE__ */ jsx(Layout, { children: run(() => {
105
+ if (state.type === "start") return /* @__PURE__ */ jsxs("form", {
106
+ "data-component": "form",
107
+ method: "post",
108
+ children: [
109
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
110
+ /* @__PURE__ */ jsx("input", {
111
+ name: "action",
112
+ type: "hidden",
113
+ value: "register"
114
+ }),
115
+ /* @__PURE__ */ jsx("input", {
116
+ type: "email",
117
+ name: "email",
118
+ placeholder: copy.input_email,
119
+ value: emailError ? "" : form?.get("email")?.toString() || "",
120
+ autoComplete: "email",
121
+ "data-component": "input",
122
+ required: true
123
+ }),
124
+ /* @__PURE__ */ jsx("input", {
125
+ type: "password",
126
+ name: "password",
127
+ placeholder: copy.input_password,
128
+ value: passwordError ? "" : form?.get("password")?.toString() || "",
129
+ autoComplete: "new-password",
130
+ "data-component": "input",
131
+ required: true
132
+ }),
133
+ /* @__PURE__ */ jsx("input", {
134
+ type: "password",
135
+ name: "repeat",
136
+ placeholder: copy.input_repeat,
137
+ autoComplete: "new-password",
138
+ "data-component": "input",
139
+ required: true
140
+ }),
141
+ /* @__PURE__ */ jsx("button", {
142
+ "data-component": "button",
225
143
  type: "submit",
226
- "data-component": "link",
227
- children: copy.code_resend
228
- })]
229
- })
230
- ]
231
- })] }) });
144
+ children: copy.button_continue
145
+ }),
146
+ /* @__PURE__ */ jsx("div", {
147
+ "data-component": "form-footer",
148
+ children: /* @__PURE__ */ jsxs("span", { children: [
149
+ copy.login_prompt,
150
+ " ",
151
+ /* @__PURE__ */ jsx("a", {
152
+ "data-component": "link",
153
+ href: "./authorize",
154
+ children: copy.login
155
+ })
156
+ ] })
157
+ })
158
+ ]
159
+ });
160
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("form", {
161
+ "data-component": "form",
162
+ method: "post",
163
+ children: [
164
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
165
+ /* @__PURE__ */ jsx("input", {
166
+ name: "action",
167
+ type: "hidden",
168
+ value: "verify"
169
+ }),
170
+ /* @__PURE__ */ jsx("input", {
171
+ type: "text",
172
+ name: "code",
173
+ placeholder: copy.input_code,
174
+ "aria-label": "6-digit verification code",
175
+ autoComplete: "one-time-code",
176
+ "data-component": "input",
177
+ inputMode: "numeric",
178
+ maxLength: 6,
179
+ minLength: 6,
180
+ pattern: "[0-9]{6}",
181
+ required: true
182
+ }),
183
+ /* @__PURE__ */ jsx("button", {
184
+ "data-component": "button",
185
+ type: "submit",
186
+ children: copy.button_continue
187
+ })
188
+ ]
189
+ }), /* @__PURE__ */ jsxs("form", {
190
+ method: "post",
191
+ children: [
192
+ /* @__PURE__ */ jsx("input", {
193
+ name: "action",
194
+ type: "hidden",
195
+ value: "register"
196
+ }),
197
+ /* @__PURE__ */ jsx("input", {
198
+ name: "email",
199
+ type: "hidden",
200
+ value: state.email
201
+ }),
202
+ /* @__PURE__ */ jsx("input", {
203
+ name: "password",
204
+ type: "hidden",
205
+ value: ""
206
+ }),
207
+ /* @__PURE__ */ jsx("input", {
208
+ name: "repeat",
209
+ type: "hidden",
210
+ value: ""
211
+ }),
212
+ /* @__PURE__ */ jsxs("div", {
213
+ "data-component": "form-footer",
214
+ children: [/* @__PURE__ */ jsxs("span", { children: [
215
+ copy.code_return,
216
+ " ",
217
+ /* @__PURE__ */ jsx("a", {
218
+ "data-component": "link",
219
+ href: "./authorize",
220
+ children: copy.login
221
+ })
222
+ ] }), /* @__PURE__ */ jsx("button", {
223
+ type: "submit",
224
+ "data-component": "link",
225
+ children: copy.code_resend
226
+ })]
227
+ })
228
+ ]
229
+ })] });
230
+ }) });
232
231
  };
233
232
  /**
234
233
  * Renders the password change form based on current state
@@ -239,124 +238,140 @@ const PasswordUI = (options) => {
239
238
  "password_mismatch",
240
239
  "validation_error"
241
240
  ].includes(error?.type || "");
242
- return /* @__PURE__ */ jsx(Layout, { children: state.type === "start" ? /* @__PURE__ */ jsxs("form", {
243
- "data-component": "form",
244
- method: "post",
245
- children: [
246
- /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
247
- /* @__PURE__ */ jsx("input", {
248
- name: "action",
249
- type: "hidden",
250
- value: "code"
251
- }),
252
- /* @__PURE__ */ jsx("input", {
253
- type: "email",
254
- name: "email",
255
- placeholder: copy.input_email,
256
- value: form?.get("email")?.toString() || "",
257
- autoComplete: "email",
258
- "data-component": "input",
259
- required: true
260
- }),
261
- /* @__PURE__ */ jsx("button", {
262
- "data-component": "button",
263
- type: "submit",
264
- children: copy.button_continue
265
- })
266
- ]
267
- }) : state.type === "code" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("form", {
268
- "data-component": "form",
269
- method: "post",
270
- children: [
271
- /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
272
- /* @__PURE__ */ jsx("input", {
273
- name: "action",
274
- type: "hidden",
275
- value: "verify"
276
- }),
277
- /* @__PURE__ */ jsx("input", {
278
- type: "text",
279
- name: "code",
280
- placeholder: copy.input_code,
281
- "aria-label": "6-digit verification code",
282
- autoComplete: "one-time-code",
283
- inputMode: "numeric",
284
- maxLength: 6,
285
- minLength: 6,
286
- "data-component": "input",
287
- pattern: "[0-9]{6}",
288
- required: true
289
- }),
290
- /* @__PURE__ */ jsx("button", {
291
- "data-component": "button",
292
- type: "submit",
293
- children: copy.button_continue
294
- })
295
- ]
296
- }), /* @__PURE__ */ jsxs("form", {
297
- method: "post",
298
- children: [
299
- /* @__PURE__ */ jsx("input", {
300
- name: "action",
301
- type: "hidden",
302
- value: "code"
303
- }),
304
- /* @__PURE__ */ jsx("input", {
305
- name: "email",
306
- type: "hidden",
307
- value: state.email
308
- }),
309
- /* @__PURE__ */ jsxs("div", {
310
- "data-component": "form-footer",
311
- children: [/* @__PURE__ */ jsxs("span", { children: [
312
- copy.code_return,
313
- " ",
314
- /* @__PURE__ */ jsx("a", {
241
+ return /* @__PURE__ */ jsx(Layout, { children: run(() => {
242
+ if (state.type === "start") return /* @__PURE__ */ jsxs("form", {
243
+ "data-component": "form",
244
+ method: "post",
245
+ children: [
246
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
247
+ /* @__PURE__ */ jsx("input", {
248
+ name: "action",
249
+ type: "hidden",
250
+ value: "code"
251
+ }),
252
+ /* @__PURE__ */ jsx("input", {
253
+ type: "email",
254
+ name: "email",
255
+ placeholder: copy.input_email,
256
+ value: form?.get("email")?.toString() || "",
257
+ autoComplete: "email",
258
+ "data-component": "input",
259
+ required: true
260
+ }),
261
+ /* @__PURE__ */ jsx("button", {
262
+ "data-component": "button",
263
+ type: "submit",
264
+ children: copy.button_continue
265
+ }),
266
+ /* @__PURE__ */ jsx("div", {
267
+ "data-component": "form-footer",
268
+ children: /* @__PURE__ */ jsxs("span", { children: [
269
+ copy.code_return,
270
+ " ",
271
+ /* @__PURE__ */ jsx("a", {
272
+ "data-component": "link",
273
+ href: "./authorize",
274
+ children: copy.login
275
+ })
276
+ ] })
277
+ })
278
+ ]
279
+ });
280
+ if (state.type === "code") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("form", {
281
+ "data-component": "form",
282
+ method: "post",
283
+ children: [
284
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
285
+ /* @__PURE__ */ jsx("input", {
286
+ name: "action",
287
+ type: "hidden",
288
+ value: "verify"
289
+ }),
290
+ /* @__PURE__ */ jsx("input", {
291
+ type: "text",
292
+ name: "code",
293
+ placeholder: copy.input_code,
294
+ "aria-label": "6-digit verification code",
295
+ autoComplete: "one-time-code",
296
+ inputMode: "numeric",
297
+ maxLength: 6,
298
+ minLength: 6,
299
+ "data-component": "input",
300
+ pattern: "[0-9]{6}",
301
+ required: true
302
+ }),
303
+ /* @__PURE__ */ jsx("button", {
304
+ "data-component": "button",
305
+ type: "submit",
306
+ children: copy.button_continue
307
+ })
308
+ ]
309
+ }), /* @__PURE__ */ jsxs("form", {
310
+ method: "post",
311
+ children: [
312
+ /* @__PURE__ */ jsx("input", {
313
+ name: "action",
314
+ type: "hidden",
315
+ value: "code"
316
+ }),
317
+ /* @__PURE__ */ jsx("input", {
318
+ name: "email",
319
+ type: "hidden",
320
+ value: state.email
321
+ }),
322
+ /* @__PURE__ */ jsxs("div", {
323
+ "data-component": "form-footer",
324
+ children: [/* @__PURE__ */ jsxs("span", { children: [
325
+ copy.code_return,
326
+ " ",
327
+ /* @__PURE__ */ jsx("a", {
328
+ "data-component": "link",
329
+ href: "./authorize",
330
+ children: copy.login
331
+ })
332
+ ] }), /* @__PURE__ */ jsx("button", {
333
+ type: "submit",
315
334
  "data-component": "link",
316
- href: "./authorize",
317
- children: copy.login
318
- })
319
- ] }), /* @__PURE__ */ jsx("button", {
335
+ children: copy.code_resend
336
+ })]
337
+ })
338
+ ]
339
+ })] });
340
+ return /* @__PURE__ */ jsxs("form", {
341
+ "data-component": "form",
342
+ method: "post",
343
+ children: [
344
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
345
+ /* @__PURE__ */ jsx("input", {
346
+ name: "action",
347
+ type: "hidden",
348
+ value: "update"
349
+ }),
350
+ /* @__PURE__ */ jsx("input", {
351
+ type: "password",
352
+ name: "password",
353
+ placeholder: copy.input_password,
354
+ value: passwordError ? "" : form?.get("password")?.toString() || "",
355
+ autoComplete: "new-password",
356
+ "data-component": "input",
357
+ required: true
358
+ }),
359
+ /* @__PURE__ */ jsx("input", {
360
+ type: "password",
361
+ name: "repeat",
362
+ placeholder: copy.input_repeat,
363
+ value: passwordError ? "" : form?.get("repeat")?.toString() || "",
364
+ autoComplete: "new-password",
365
+ "data-component": "input",
366
+ required: true
367
+ }),
368
+ /* @__PURE__ */ jsx("button", {
369
+ "data-component": "button",
320
370
  type: "submit",
321
- "data-component": "link",
322
- children: copy.code_resend
323
- })]
324
- })
325
- ]
326
- })] }) : /* @__PURE__ */ jsxs("form", {
327
- "data-component": "form",
328
- method: "post",
329
- children: [
330
- /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
331
- /* @__PURE__ */ jsx("input", {
332
- name: "action",
333
- type: "hidden",
334
- value: "update"
335
- }),
336
- /* @__PURE__ */ jsx("input", {
337
- type: "password",
338
- name: "password",
339
- placeholder: copy.input_password,
340
- value: passwordError ? "" : form?.get("password")?.toString() || "",
341
- autoComplete: "new-password",
342
- "data-component": "input",
343
- required: true
344
- }),
345
- /* @__PURE__ */ jsx("input", {
346
- type: "password",
347
- name: "repeat",
348
- placeholder: copy.input_repeat,
349
- value: passwordError ? "" : form?.get("repeat")?.toString() || "",
350
- autoComplete: "new-password",
351
- "data-component": "input",
352
- required: true
353
- }),
354
- /* @__PURE__ */ jsx("button", {
355
- "data-component": "button",
356
- type: "submit",
357
- children: copy.button_continue
358
- })
359
- ]
371
+ children: copy.button_continue
372
+ })
373
+ ]
374
+ });
360
375
  }) });
361
376
  };
362
377
  return {
package/dist/util.d.mts CHANGED
@@ -68,5 +68,28 @@ declare const isDomainMatch: (a: string, b: string) => boolean;
68
68
  * ```
69
69
  */
70
70
  declare const lazy: <T>(fn: () => T) => (() => T);
71
+ /**
72
+ * Utility function to immediately invoke a function and return its result.
73
+ * Useful for complex conditional rendering logic in JSX/TSX where you want
74
+ * to use if/else statements instead of ternary operators.
75
+ *
76
+ * @template T - The return type of the function
77
+ * @param fn - Function to execute immediately
78
+ * @returns The result of executing the function
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * return (
83
+ * <div>
84
+ * {run(() => {
85
+ * if (state === "loading") return <Spinner />
86
+ * if (state === "error") return <Error />
87
+ * return <Content />
88
+ * })}
89
+ * </div>
90
+ * )
91
+ * ```
92
+ */
93
+ declare const run: <T>(fn: () => T) => T;
71
94
  //#endregion
72
- export { Prettify, getRelativeUrl, isDomainMatch, lazy };
95
+ export { Prettify, getRelativeUrl, isDomainMatch, lazy, run };
package/dist/util.mjs CHANGED
@@ -100,6 +100,29 @@ const lazy = (fn) => {
100
100
  return value;
101
101
  };
102
102
  };
103
+ /**
104
+ * Utility function to immediately invoke a function and return its result.
105
+ * Useful for complex conditional rendering logic in JSX/TSX where you want
106
+ * to use if/else statements instead of ternary operators.
107
+ *
108
+ * @template T - The return type of the function
109
+ * @param fn - Function to execute immediately
110
+ * @returns The result of executing the function
111
+ *
112
+ * @example
113
+ * ```tsx
114
+ * return (
115
+ * <div>
116
+ * {run(() => {
117
+ * if (state === "loading") return <Spinner />
118
+ * if (state === "error") return <Error />
119
+ * return <Content />
120
+ * })}
121
+ * </div>
122
+ * )
123
+ * ```
124
+ */
125
+ const run = (fn) => fn();
103
126
 
104
127
  //#endregion
105
- export { getRelativeUrl, isDomainMatch, lazy };
128
+ export { getRelativeUrl, isDomainMatch, lazy, run };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draftlab/auth",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "Core implementation for @draftlab/auth",
6
6
  "author": "Matheus Pergoli",