@rpcbase/auth 0.59.0 → 0.60.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.
@@ -0,0 +1,5 @@
1
+ import { Api } from '../../../../api/src';
2
+ import { AuthSessionUser } from '../../types';
3
+ declare const _default: (api: Api<AuthSessionUser>) => void;
4
+ export default _default;
5
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/resend-otp/handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;AAInD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBAqDlC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
@@ -0,0 +1,12 @@
1
+ import { z } from '../../../../vite/node_modules/zod';
2
+ export declare const Route = "/api/rb/auth/resend-otp";
3
+ export declare const requestSchema: z.ZodObject<{
4
+ email: z.ZodString;
5
+ }, z.core.$strip>;
6
+ export type RequestPayload = z.infer<typeof requestSchema>;
7
+ export declare const responseSchema: z.ZodObject<{
8
+ success: z.ZodBoolean;
9
+ error: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ export type ResponsePayload = z.infer<typeof responseSchema>;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/resend-otp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,KAAK,4BAA4B,CAAA;AAE9C,eAAO,MAAM,aAAa;;iBAExB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;iBAGzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SignInForm/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAY,MAAM,OAAO,CAAA;AAW1C,eAAO,MAAM,UAAU,GAAI,yBAGxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,4CA6CA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SignInForm/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AA6CjC,eAAO,MAAM,UAAU,GAAI,yBAGxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,4CAuDA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SignUpForm/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAsB,MAAM,OAAO,CAAA;AAYpD,eAAO,MAAM,UAAU,GAAI,sCAIxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,4CA8EA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SignUpForm/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAW,MAAM,OAAO,CAAA;AAYzC,eAAO,MAAM,UAAU,GAAI,sCAIxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,4CAuEA,CAAA"}
@@ -0,0 +1,51 @@
1
+ import crypto from "crypto";
2
+ import { loadModel } from "@rpcbase/db";
3
+ import { sendEmail } from "@rpcbase/server";
4
+ import { o as object, s as string, b as boolean } from "./schemas-KL7REOdt.js";
5
+ const Route = "/api/rb/auth/resend-otp";
6
+ const requestSchema = object({
7
+ email: string().email()
8
+ });
9
+ object({
10
+ success: boolean(),
11
+ error: string().optional()
12
+ });
13
+ const resendOtp = async (payload, ctx) => {
14
+ const User = await loadModel("RBUser", ctx);
15
+ const parsed = requestSchema.safeParse(payload);
16
+ if (!parsed.success) {
17
+ ctx.res.status(400);
18
+ return { success: false, error: "invalid_payload" };
19
+ }
20
+ const { email } = parsed.data;
21
+ const user = await User.findOne({ email });
22
+ if (!user) {
23
+ ctx.res.status(404);
24
+ return { success: false, error: "user_not_found" };
25
+ }
26
+ const emailVerificationCode = crypto.randomInt(0, 1e6).toString().padStart(6, "0");
27
+ const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1e3);
28
+ user.email_verification_code = emailVerificationCode;
29
+ user.email_verification_expires_at = emailVerificationExpiresAt;
30
+ await user.save();
31
+ try {
32
+ await sendEmail({
33
+ to: email,
34
+ subject: `Your verification code: ${emailVerificationCode}`,
35
+ html: `
36
+ <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>
37
+ <p>If you didn't request this, you can ignore this message.</p>
38
+ `,
39
+ text: `Your verification code is ${emailVerificationCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`
40
+ });
41
+ } catch (err) {
42
+ console.warn("failed to resend otp email", err);
43
+ }
44
+ return { success: true };
45
+ };
46
+ const handler = (api) => {
47
+ api.post(Route, resendOtp);
48
+ };
49
+ export {
50
+ handler as default
51
+ };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useLocation, Link, useNavigate } from "@rpcbase/router";
2
+ import { useLocation, Link, useNavigate, useSearchParams } from "@rpcbase/router";
3
3
  import clsx from "clsx";
4
4
  import { useFormContext, useForm, zodResolver, FormProvider } from "@rpcbase/form";
5
- import * as React from "react";
6
- import { useEffect, useState, useCallback, useMemo } from "react";
7
5
  import { r as requestSchema } from "./index-Bdcryyvv.js";
6
+ import * as React from "react";
7
+ import { useState, useEffect, useCallback, useMemo } from "react";
8
8
  import { r as requestSchema$1 } from "./index-DwX0Y2YV.js";
9
9
  import { b, a, r } from "./middleware-BiMXO6Dq.js";
10
10
  const LINKS_REDIRECTION_MAP = {
@@ -108,10 +108,40 @@ const EmailInput = ({
108
108
  errorMessage ? /* @__PURE__ */ jsx("p", { className: "mt-1 -mb-2 text-sm/6 text-red-500", children: errorMessage }) : null
109
109
  ] });
110
110
  };
111
+ const hasUnsafeNextPathChars = (value) => {
112
+ for (let i = 0; i < value.length; i++) {
113
+ const code = value.charCodeAt(i);
114
+ if (code === 92 || code <= 31 || code === 127) return true;
115
+ }
116
+ return false;
117
+ };
118
+ const sanitizeNextPath = (raw) => {
119
+ if (!raw) return null;
120
+ const nextPath = raw.trim();
121
+ if (!nextPath) return null;
122
+ if (!nextPath.startsWith("/") || nextPath.startsWith("//")) {
123
+ return null;
124
+ }
125
+ if (hasUnsafeNextPathChars(nextPath)) {
126
+ return null;
127
+ }
128
+ try {
129
+ const base = new URL("http://localhost");
130
+ const url = new URL(nextPath, base);
131
+ if (url.origin !== base.origin) {
132
+ return null;
133
+ }
134
+ return `${url.pathname}${url.search}${url.hash}`;
135
+ } catch {
136
+ return null;
137
+ }
138
+ };
111
139
  const SignInForm = ({
112
140
  children,
113
141
  className
114
142
  }) => {
143
+ const navigate = useNavigate();
144
+ const [searchParams] = useSearchParams();
115
145
  const methods = useForm({
116
146
  defaultValues: {
117
147
  email: "",
@@ -121,28 +151,32 @@ const SignInForm = ({
121
151
  resolver: zodResolver(requestSchema)
122
152
  });
123
153
  const onSubmit = async (data) => {
124
- console.log("SUBMIT SIGNIN", data);
154
+ methods.clearErrors("root");
125
155
  try {
126
156
  const res = await fetch("/api/rb/auth/sign-in", {
127
157
  method: "POST",
128
158
  headers: {
129
159
  "Content-Type": "application/json"
130
160
  },
131
- body: JSON.stringify(data)
161
+ body: JSON.stringify(data),
162
+ credentials: "include"
132
163
  });
133
- const json = await res.json();
134
- console.log("SIGN IN RESPONSE", json);
135
- if (!res.ok) {
136
- console.error("Sign-in failed", json);
164
+ const json = await res.json().catch(() => null);
165
+ if (!res.ok || !json?.success) {
166
+ const message = json?.error === "invalid_credentials" ? "Invalid email or password." : "Sign-in failed. Please try again.";
167
+ methods.setError("root", { type: "server", message });
168
+ return;
137
169
  }
170
+ const nextPath = sanitizeNextPath(searchParams.get("next")) ?? "/";
171
+ navigate(nextPath, { replace: true });
138
172
  } catch (err) {
139
- console.error("Sign-in request error", err);
173
+ methods.setError("root", { type: "server", message: "Network error. Please try again." });
140
174
  }
141
175
  };
142
- useEffect(() => {
143
- console.log(methods.formState.errors);
144
- }, [methods.formState.errors]);
145
- return /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsx("form", { method: "post", noValidate: true, className, onSubmit: methods.handleSubmit(onSubmit), children }) });
176
+ return /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsxs("form", { method: "post", noValidate: true, className, onSubmit: methods.handleSubmit(onSubmit), children: [
177
+ children,
178
+ methods.formState.errors.root?.message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-red-600", role: "alert", children: methods.formState.errors.root.message })
179
+ ] }) });
146
180
  };
147
181
  const SignUpForm = ({
148
182
  children,
@@ -162,19 +196,17 @@ const SignUpForm = ({
162
196
  const onSubmit = async (data) => {
163
197
  setServerMessage(null);
164
198
  methods.clearErrors("root");
165
- console.log("SUBMIT SIGNUp", data);
166
199
  try {
167
200
  const res = await fetch("/api/rb/auth/sign-up", {
168
201
  method: "POST",
169
202
  headers: {
170
203
  "Content-Type": "application/json"
171
204
  },
172
- body: JSON.stringify(data)
205
+ body: JSON.stringify(data),
206
+ credentials: "include"
173
207
  });
174
208
  const json = await res.json();
175
- console.log("SIGN UP RESPONSE", json);
176
209
  if (!res.ok) {
177
- console.error("Sign-up failed", json);
178
210
  const message = json.error === "user_exists" ? "An account already exists with this email." : "Sign-up failed. Please try again.";
179
211
  methods.setError("root", { type: "server", message });
180
212
  return;
@@ -190,13 +222,9 @@ const SignUpForm = ({
190
222
  navigate(`/auth/sign-up-otp?${search.toString()}`);
191
223
  setServerMessage("Account created. Check your inbox to verify your email.");
192
224
  } catch (err) {
193
- console.error("Sign-up request error", err);
194
225
  methods.setError("root", { type: "server", message: "Network error. Please try again." });
195
226
  }
196
227
  };
197
- useEffect(() => {
198
- console.log(methods.formState.errors);
199
- }, [methods.formState.errors]);
200
228
  return /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsxs("form", { method: "post", noValidate: true, className, onSubmit: methods.handleSubmit(onSubmit), children: [
201
229
  children,
202
230
  methods.formState.errors.root?.message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-red-600", role: "alert", children: methods.formState.errors.root.message }),
@@ -326,13 +354,13 @@ const useResendCountdown = (seconds = 60) => {
326
354
  setRemaining((prev) => {
327
355
  if (prev <= 1) {
328
356
  setIsCountingDown(false);
329
- return 0;
357
+ return seconds;
330
358
  }
331
359
  return prev - 1;
332
360
  });
333
361
  }, 1e3);
334
362
  return () => clearInterval(timer);
335
- }, [isCountingDown]);
363
+ }, [isCountingDown, seconds]);
336
364
  const restart = useCallback((nextSeconds) => {
337
365
  const value = typeof nextSeconds === "number" ? nextSeconds : seconds;
338
366
  setRemaining(value);
@@ -343,7 +371,7 @@ const useResendCountdown = (seconds = 60) => {
343
371
  const secs = remaining % 60;
344
372
  return `${minutes}:${secs.toString().padStart(2, "0")}`;
345
373
  }, [remaining]);
346
- const canResend = !isCountingDown && remaining === 0;
374
+ const canResend = !isCountingDown;
347
375
  return { remaining, formatted, isCountingDown, canResend, restart };
348
376
  };
349
377
  const ResendCodeButton = ({
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const routes = Object.entries({
2
- .../* @__PURE__ */ Object.assign({ "./api/me/handler.ts": () => import("./handler-C9aNvw-Q.js"), "./api/sign-in/handler.ts": () => import("./handler-ByODvDJo.js"), "./api/sign-out/handler.ts": () => import("./handler-CNHucHrj.js"), "./api/sign-up/handler.ts": () => import("./handler-DgTUP3cD.js"), "./api/verify-otp/handler.ts": () => import("./handler-49961uAb.js") })
2
+ .../* @__PURE__ */ Object.assign({ "./api/me/handler.ts": () => import("./handler-C9aNvw-Q.js"), "./api/resend-otp/handler.ts": () => import("./handler-BxA7Jhrs.js"), "./api/sign-in/handler.ts": () => import("./handler-ByODvDJo.js"), "./api/sign-out/handler.ts": () => import("./handler-CNHucHrj.js"), "./api/sign-up/handler.ts": () => import("./handler-DgTUP3cD.js"), "./api/verify-otp/handler.ts": () => import("./handler-49961uAb.js") })
3
3
  }).reduce((acc, [path, mod]) => {
4
4
  acc[path.replace("./api/", "@rpcbase/auth/api/")] = mod;
5
5
  return acc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/auth",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",