@rpcbase/auth 0.45.0 → 0.47.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.
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/sign-in/handler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAA;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBAgElC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/sign-in/handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAA;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBA4DlC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
@@ -1,7 +1,7 @@
1
1
  import { z } from '../../../../vite/node_modules/zod';
2
2
  export declare const Route = "/api/rb/auth/sign-in";
3
3
  export declare const requestSchema: z.ZodObject<{
4
- email_or_phone: z.ZodDefault<z.ZodString>;
4
+ email: z.ZodString;
5
5
  password: z.ZodString;
6
6
  remember_me: z.ZodDefault<z.ZodBoolean>;
7
7
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/sign-in/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,KAAK,yBAAyB,CAAA;AAE3C,eAAO,MAAM,aAAa;;;;iBAexB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/sign-in/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,KAAK,yBAAyB,CAAA;AAE3C,eAAO,MAAM,aAAa;;;;iBAOxB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/sign-up/handler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAA;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBAwFlC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/sign-up/handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAA;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBAgFlC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
@@ -1,9 +1,8 @@
1
1
  import { z } from '../../../../vite/node_modules/zod';
2
2
  export declare const Route = "/api/rb/auth/sign-up";
3
3
  export declare const requestSchema: z.ZodObject<{
4
- email_or_phone: z.ZodString;
4
+ email: z.ZodString;
5
5
  password: z.ZodString;
6
- password_confirmation: z.ZodString;
7
6
  remember_me: z.ZodDefault<z.ZodBoolean>;
8
7
  }, z.core.$strip>;
9
8
  export type RequestPayload = z.infer<typeof requestSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/sign-up/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,KAAK,yBAAyB,CAAA;AAE3C,eAAO,MAAM,aAAa;;;;;iBAoBtB,CAAA;AAEJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/sign-up/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,KAAK,yBAAyB,CAAA;AAE3C,eAAO,MAAM,aAAa;;;;iBAQtB,CAAA;AAEJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -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/verify-otp/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAA8B,MAAM,cAAc,CAAA;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;yBA0DlC,KAAK,GAAG,CAAC,eAAe,CAAC;AAAzC,wBAEC"}
@@ -0,0 +1,16 @@
1
+ import { z } from '../../../../vite/node_modules/zod';
2
+ export declare const Route = "/api/rb/auth/verify-otp";
3
+ export declare const requestSchema: z.ZodObject<{
4
+ email: z.ZodString;
5
+ code: z.ZodString;
6
+ remember_me: z.ZodDefault<z.ZodBoolean>;
7
+ }, z.core.$strip>;
8
+ export type RequestPayload = z.infer<typeof requestSchema>;
9
+ export declare const responseSchema: z.ZodObject<{
10
+ success: z.ZodBoolean;
11
+ error: z.ZodOptional<z.ZodString>;
12
+ user_id: z.ZodOptional<z.ZodString>;
13
+ tenant_id: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ export type ResponsePayload = z.infer<typeof responseSchema>;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/verify-otp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,KAAK,4BAA4B,CAAA;AAE9C,eAAO,MAAM,aAAa;;;;iBAIxB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -1,4 +1,4 @@
1
- export declare const EmailOrPhoneInput: ({ id, className, placeholder, }: {
1
+ export declare const EmailInput: ({ id, className, placeholder, }: {
2
2
  id: string;
3
3
  className?: string;
4
4
  placeholder?: string;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/EmailInput/index.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,GAAI,iCAIxB;IACD,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,4CA2BA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PasswordInput/index.tsx"],"names":[],"mappings":"AAGA,KAAK,kBAAkB,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,qDAM3B,kBAAkB,4CA0BpB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PasswordInput/index.tsx"],"names":[],"mappings":"AAKA,KAAK,kBAAkB,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,qDAM3B,kBAAkB,4CA2CpB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
- export declare const SignUpForm: ({ children, className }: {
2
+ export declare const SignUpForm: ({ children, className, otpNextPath }: {
3
3
  children: ReactNode;
4
4
  className?: string;
5
+ otpNextPath?: string;
5
6
  }) => import("react/jsx-runtime").JSX.Element;
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -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;AAWpD,eAAO,MAAM,UAAU,GAAI,yBAGxB;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,4CAyEA,CAAA"}
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,6 +1,6 @@
1
1
  export * from './AuthLayout';
2
2
  export * from './AppleSignInButton';
3
- export * from './EmailOrPhoneInput';
3
+ export * from './EmailInput';
4
4
  export * from './SignInForm';
5
5
  export * from './SignUpForm';
6
6
  export * from './RememberMeCheckbox';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA"}
@@ -0,0 +1,64 @@
1
+ import crypto from "crypto";
2
+ import { loadModel } from "@rpcbase/api";
3
+ import { hashPassword, sendEmail } from "@rpcbase/server";
4
+ import { R as Route, r as requestSchema } from "./index-DwX0Y2YV.js";
5
+ const signUp = async (payload, ctx) => {
6
+ const User = await loadModel("User", ctx);
7
+ const Tenant = await loadModel("Tenant", ctx);
8
+ const parsed = requestSchema.safeParse(payload);
9
+ if (!parsed.success) {
10
+ ctx.res.status(400);
11
+ return { success: false, error: "invalid_payload" };
12
+ }
13
+ const { email, password, remember_me: _remember_me } = parsed.data;
14
+ const existingUser = await User.findOne({ email });
15
+ if (existingUser) {
16
+ console.log("user with email already exists", email);
17
+ ctx.res.status(409);
18
+ return { success: false, error: "user_exists" };
19
+ }
20
+ const salt = crypto.randomBytes(16).toString("hex");
21
+ const derivedKey = await hashPassword(password, salt);
22
+ const hashedPassword = `${salt}:${derivedKey.toString("hex")}`;
23
+ const tenantId = crypto.randomUUID();
24
+ const emailVerificationCode = crypto.randomInt(0, 1e6).toString().padStart(6, "0");
25
+ const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1e3);
26
+ const user = new User({
27
+ email,
28
+ password: hashedPassword,
29
+ tenants: [tenantId],
30
+ email_verification_code: emailVerificationCode,
31
+ email_verification_expires_at: emailVerificationExpiresAt
32
+ });
33
+ await user.save();
34
+ try {
35
+ await sendEmail({
36
+ to: email,
37
+ subject: `Verify your email: ${emailVerificationCode}`,
38
+ html: `
39
+ <p>Welcome to rpcbase!</p>
40
+ <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>
41
+ <p>If you didn't request this, you can ignore this message.</p>
42
+ `,
43
+ text: `Welcome to rpcbase! Your verification code is ${emailVerificationCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`
44
+ });
45
+ } catch (err) {
46
+ console.warn("failed to send sign-up email", err);
47
+ }
48
+ try {
49
+ await Tenant.create({
50
+ tenant_id: tenantId,
51
+ name: email
52
+ });
53
+ } catch (err) {
54
+ console.warn("failed to create tenant for user", err);
55
+ }
56
+ console.log("created new user", user._id.toString());
57
+ return { success: true, user_id: user._id.toString(), tenant_id: tenantId };
58
+ };
59
+ const handler = (api) => {
60
+ api.post(Route, signUp);
61
+ };
62
+ export {
63
+ handler as default
64
+ };
@@ -0,0 +1,57 @@
1
+ import { loadModel } from "@rpcbase/api";
2
+ import { o as object, b as boolean, s as string } from "./schemas-KL7REOdt.js";
3
+ const Route = "/api/rb/auth/verify-otp";
4
+ const requestSchema = object({
5
+ email: string().email(),
6
+ code: string().length(6, "Code must be 6 digits"),
7
+ remember_me: boolean().default(true)
8
+ });
9
+ object({
10
+ success: boolean(),
11
+ error: string().optional(),
12
+ user_id: string().optional(),
13
+ tenant_id: string().optional()
14
+ });
15
+ const verifyOtp = async (payload, ctx) => {
16
+ const User = await loadModel("User", ctx);
17
+ const parsed = requestSchema.safeParse(payload);
18
+ if (!parsed.success) {
19
+ ctx.res.status(400);
20
+ return { success: false, error: "invalid_payload" };
21
+ }
22
+ const { email, code } = parsed.data;
23
+ const user = await User.findOne({ email }, { email_verification_code: 1, email_verification_expires_at: 1, tenants: 1 });
24
+ if (!user) {
25
+ ctx.res.status(404);
26
+ return { success: false, error: "user_not_found" };
27
+ }
28
+ const storedCode = user.email_verification_code;
29
+ const expiresAt = user.email_verification_expires_at;
30
+ const isExpired = expiresAt instanceof Date && expiresAt.getTime() < Date.now();
31
+ if (!storedCode || storedCode !== code || isExpired) {
32
+ ctx.res.status(400);
33
+ return { success: false, error: "invalid_code" };
34
+ }
35
+ user.email_verification_code = void 0;
36
+ user.email_verification_expires_at = void 0;
37
+ await user.save();
38
+ const tenantId = user.tenants?.[0]?.toString?.() || "00000000";
39
+ const signedInTenants = (user.tenants || []).map((t) => t.toString?.() || String(t)) || [tenantId];
40
+ if (!ctx.req.session) {
41
+ ctx.res.status(500);
42
+ return { success: false, error: "session_unavailable" };
43
+ }
44
+ ctx.req.session.user = {
45
+ id: user._id.toString(),
46
+ current_tenant_id: tenantId,
47
+ signed_in_tenants: signedInTenants.length ? signedInTenants : [tenantId],
48
+ is_entry_gate_authorized: true
49
+ };
50
+ return { success: true, user_id: user._id.toString(), tenant_id: tenantId };
51
+ };
52
+ const handler = (api) => {
53
+ api.post(Route, verifyOtp);
54
+ };
55
+ export {
56
+ handler as default
57
+ };
@@ -1,8 +1,7 @@
1
1
  import crypto from "crypto";
2
- import { i as isEmail } from "./isEmail-IG0hXiQk.js";
3
2
  import { loadModel } from "@rpcbase/api";
4
3
  import { hashPassword } from "@rpcbase/server";
5
- import { R as Route, r as requestSchema } from "./index-r56vqjph.js";
4
+ import { R as Route, r as requestSchema } from "./index-Bdcryyvv.js";
6
5
  const signIn = async (payload, ctx) => {
7
6
  const User = await loadModel("User", ctx);
8
7
  const parsed = requestSchema.safeParse(payload);
@@ -10,9 +9,8 @@ const signIn = async (payload, ctx) => {
10
9
  ctx.res.status(400);
11
10
  return { success: false, error: "invalid_payload" };
12
11
  }
13
- const { email_or_phone, password } = parsed.data;
14
- const query = isEmail(email_or_phone) ? { email: email_or_phone } : { phone: email_or_phone };
15
- const user = await User.findOne(query, { password: 1, tenants: 1 });
12
+ const { email, password } = parsed.data;
13
+ const user = await User.findOne({ email }, { password: 1, tenants: 1 });
16
14
  if (!user?.password) {
17
15
  ctx.res.status(401);
18
16
  return { success: false, error: "invalid_credentials" };
@@ -1,15 +1,7 @@
1
- import { i as isValidNumber } from "./isValidNumber-6pMDGLRn.js";
2
1
  import { o as object, b as boolean, s as string } from "./schemas-KL7REOdt.js";
3
2
  const Route = "/api/rb/auth/sign-in";
4
3
  const requestSchema = object({
5
- email_or_phone: string().nonempty("Email or phone number is required").default("").refine(
6
- (value) => {
7
- const isEmail = string().email().safeParse(value).success;
8
- const isPhone = isValidNumber(value);
9
- return isEmail || isPhone;
10
- },
11
- "Please enter a valid email address or phone number"
12
- ),
4
+ email: string().nonempty("Email is required").email("Please enter a valid email address"),
13
5
  password: string().min(1, { message: "Password is required" }),
14
6
  remember_me: boolean().default(true)
15
7
  });
@@ -0,0 +1,17 @@
1
+ import { o as object, b as boolean, s as string } from "./schemas-KL7REOdt.js";
2
+ const Route = "/api/rb/auth/sign-up";
3
+ const requestSchema = object({
4
+ email: string().nonempty("Email is required").email("Please enter a valid email address"),
5
+ password: string().min(8, { message: "Password must be at least 8 characters long." }),
6
+ remember_me: boolean().default(true)
7
+ });
8
+ object({
9
+ success: boolean(),
10
+ error: string().optional(),
11
+ user_id: string().optional(),
12
+ tenant_id: string().optional()
13
+ });
14
+ export {
15
+ Route as R,
16
+ requestSchema as r
17
+ };
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useLocation, Link } from "@rpcbase/router";
2
+ import { useLocation, Link, useNavigate } 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";
5
6
  import { useEffect, useState } from "react";
6
- import { r as requestSchema } from "./index-r56vqjph.js";
7
- import { r as requestSchema$1 } from "./index-Cq04nmsE.js";
7
+ import { r as requestSchema } from "./index-Bdcryyvv.js";
8
+ import { r as requestSchema$1 } from "./index-DwX0Y2YV.js";
8
9
  import { b, a, r } from "./middleware-BiMXO6Dq.js";
9
10
  const LINKS_REDIRECTION_MAP = {
10
11
  "/auth/sign-in": {
@@ -78,13 +79,13 @@ const AppleSignInButton = ({
78
79
  }
79
80
  );
80
81
  };
81
- const EmailOrPhoneInput = ({
82
+ const EmailInput = ({
82
83
  id,
83
84
  className,
84
85
  placeholder
85
86
  }) => {
86
87
  const { register, formState } = useFormContext();
87
- const error = formState.errors.email_or_phone;
88
+ const error = formState.errors.email;
88
89
  let errorMessage;
89
90
  if (typeof error === "string") {
90
91
  errorMessage = error;
@@ -96,12 +97,12 @@ const EmailOrPhoneInput = ({
96
97
  "input",
97
98
  {
98
99
  id,
99
- type: "text",
100
+ type: "email",
100
101
  required: true,
101
- autoComplete: "on",
102
+ autoComplete: "email",
102
103
  className,
103
104
  placeholder,
104
- ...register("email_or_phone")
105
+ ...register("email")
105
106
  }
106
107
  ),
107
108
  errorMessage ? /* @__PURE__ */ jsx("p", { className: "mt-1 -mb-2 text-sm/6 text-red-500", children: errorMessage }) : null
@@ -113,7 +114,7 @@ const SignInForm = ({
113
114
  }) => {
114
115
  const methods = useForm({
115
116
  defaultValues: {
116
- email_or_phone: "",
117
+ email: "",
117
118
  password: "",
118
119
  remember_me: true
119
120
  },
@@ -145,14 +146,15 @@ const SignInForm = ({
145
146
  };
146
147
  const SignUpForm = ({
147
148
  children,
148
- className
149
+ className,
150
+ otpNextPath
149
151
  }) => {
152
+ const navigate = useNavigate();
150
153
  const [serverMessage, setServerMessage] = useState(null);
151
154
  const methods = useForm({
152
155
  defaultValues: {
153
- email_or_phone: "",
156
+ email: "",
154
157
  password: "",
155
- password_confirmation: "",
156
158
  remember_me: true
157
159
  },
158
160
  resolver: zodResolver(requestSchema$1)
@@ -173,7 +175,7 @@ const SignUpForm = ({
173
175
  console.log("SIGN UP RESPONSE", json);
174
176
  if (!res.ok) {
175
177
  console.error("Sign-up failed", json);
176
- const message = json.error === "user_exists" ? "An account already exists with this email or phone." : "Sign-up failed. Please try again.";
178
+ const message = json.error === "user_exists" ? "An account already exists with this email." : "Sign-up failed. Please try again.";
177
179
  methods.setError("root", { type: "server", message });
178
180
  return;
179
181
  }
@@ -181,8 +183,12 @@ const SignUpForm = ({
181
183
  methods.setError("root", { type: "server", message: "Sign-up failed. Please try again." });
182
184
  return;
183
185
  }
186
+ const search = new URLSearchParams({ email: data.email });
187
+ if (otpNextPath) {
188
+ search.set("next", otpNextPath);
189
+ }
190
+ navigate(`/auth/sign-up-otp?${search.toString()}`);
184
191
  setServerMessage("Account created. Check your inbox to verify your email.");
185
- methods.reset();
186
192
  } catch (err) {
187
193
  console.error("Sign-up request error", err);
188
194
  methods.setError("root", { type: "server", message: "Network error. Please try again." });
@@ -220,6 +226,58 @@ const RememberMeCheckbox = ({
220
226
  )
221
227
  ] });
222
228
  };
229
+ function EyeSlashIcon({
230
+ title,
231
+ titleId,
232
+ ...props
233
+ }, svgRef) {
234
+ return /* @__PURE__ */ React.createElement("svg", Object.assign({
235
+ xmlns: "http://www.w3.org/2000/svg",
236
+ fill: "none",
237
+ viewBox: "0 0 24 24",
238
+ strokeWidth: 1.5,
239
+ stroke: "currentColor",
240
+ "aria-hidden": "true",
241
+ "data-slot": "icon",
242
+ ref: svgRef,
243
+ "aria-labelledby": titleId
244
+ }, props), title ? /* @__PURE__ */ React.createElement("title", {
245
+ id: titleId
246
+ }, title) : null, /* @__PURE__ */ React.createElement("path", {
247
+ strokeLinecap: "round",
248
+ strokeLinejoin: "round",
249
+ d: "M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
250
+ }));
251
+ }
252
+ const ForwardRef$1 = /* @__PURE__ */ React.forwardRef(EyeSlashIcon);
253
+ function EyeIcon({
254
+ title,
255
+ titleId,
256
+ ...props
257
+ }, svgRef) {
258
+ return /* @__PURE__ */ React.createElement("svg", Object.assign({
259
+ xmlns: "http://www.w3.org/2000/svg",
260
+ fill: "none",
261
+ viewBox: "0 0 24 24",
262
+ strokeWidth: 1.5,
263
+ stroke: "currentColor",
264
+ "aria-hidden": "true",
265
+ "data-slot": "icon",
266
+ ref: svgRef,
267
+ "aria-labelledby": titleId
268
+ }, props), title ? /* @__PURE__ */ React.createElement("title", {
269
+ id: titleId
270
+ }, title) : null, /* @__PURE__ */ React.createElement("path", {
271
+ strokeLinecap: "round",
272
+ strokeLinejoin: "round",
273
+ d: "M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
274
+ }), /* @__PURE__ */ React.createElement("path", {
275
+ strokeLinecap: "round",
276
+ strokeLinejoin: "round",
277
+ d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
278
+ }));
279
+ }
280
+ const ForwardRef = /* @__PURE__ */ React.forwardRef(EyeIcon);
223
281
  const PasswordInput = ({
224
282
  id,
225
283
  name = "password",
@@ -227,28 +285,42 @@ const PasswordInput = ({
227
285
  placeholder,
228
286
  autoComplete = "current-password"
229
287
  }) => {
288
+ const [showPassword, setShowPassword] = useState(false);
230
289
  const { register, formState } = useFormContext();
231
290
  const fieldError = formState.errors[name];
232
291
  const errorMessage = typeof fieldError === "string" ? fieldError : typeof fieldError?.message === "string" ? fieldError.message : void 0;
292
+ const inputClassName = className ? `${className} pr-11` : "pr-11";
233
293
  return /* @__PURE__ */ jsxs(Fragment, { children: [
234
- /* @__PURE__ */ jsx(
235
- "input",
236
- {
237
- id,
238
- type: "password",
239
- autoComplete,
240
- className,
241
- placeholder,
242
- ...register(name)
243
- }
244
- ),
294
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
295
+ /* @__PURE__ */ jsx(
296
+ "input",
297
+ {
298
+ id,
299
+ type: showPassword ? "text" : "password",
300
+ autoComplete,
301
+ className: inputClassName,
302
+ placeholder,
303
+ ...register(name)
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsx(
307
+ "button",
308
+ {
309
+ type: "button",
310
+ className: "absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500",
311
+ onClick: () => setShowPassword((prev) => !prev),
312
+ "aria-label": showPassword ? "Hide characters" : "Show characters",
313
+ children: showPassword ? /* @__PURE__ */ jsx(ForwardRef$1, { className: "h-5 w-5", "aria-hidden": true }) : /* @__PURE__ */ jsx(ForwardRef, { className: "h-5 w-5", "aria-hidden": true })
314
+ }
315
+ )
316
+ ] }),
245
317
  errorMessage ? /* @__PURE__ */ jsx("p", { className: "mt-1 -mb-2 text-sm/6 text-red-500", children: errorMessage }) : null
246
318
  ] });
247
319
  };
248
320
  export {
249
321
  AppleSignInButton,
250
322
  AuthLayout,
251
- EmailOrPhoneInput,
323
+ EmailInput,
252
324
  PasswordInput,
253
325
  RememberMeCheckbox,
254
326
  SignInForm,
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-Ba3pgtfZ.js"), "./api/sign-in/handler.ts": () => import("./handler-BSoyBLZM.js"), "./api/sign-out/handler.ts": () => import("./handler-CNHucHrj.js"), "./api/sign-up/handler.ts": () => import("./handler-rZR_dx7n.js") })
2
+ .../* @__PURE__ */ Object.assign({ "./api/me/handler.ts": () => import("./handler-Ba3pgtfZ.js"), "./api/sign-in/handler.ts": () => import("./handler-r4ZECW_z.js"), "./api/sign-out/handler.ts": () => import("./handler-CNHucHrj.js"), "./api/sign-up/handler.ts": () => import("./handler-CE4lXc0G.js"), "./api/verify-otp/handler.ts": () => import("./handler-DOnLMd-9.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.45.0",
3
+ "version": "0.47.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -38,6 +38,13 @@
38
38
  "dist/"
39
39
  ]
40
40
  },
41
+ "build-watch": {
42
+ "command": "../../node_modules/.bin/vite build --watch",
43
+ "service": true,
44
+ "dependencies": [
45
+ "build"
46
+ ]
47
+ },
41
48
  "release": {
42
49
  "command": "../../scripts/publish.js",
43
50
  "dependencies": [
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/EmailOrPhoneInput/index.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAI,iCAI/B;IACD,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,4CA6BA,CAAA"}
@@ -1,71 +0,0 @@
1
- import crypto from "crypto";
2
- import { i as isEmail } from "./isEmail-IG0hXiQk.js";
3
- import { loadModel } from "@rpcbase/api";
4
- import { hashPassword, sendEmail } from "@rpcbase/server";
5
- import { R as Route, r as requestSchema } from "./index-Cq04nmsE.js";
6
- const signUp = async (payload, ctx) => {
7
- const User = await loadModel("User", ctx);
8
- const Tenant = await loadModel("Tenant", ctx);
9
- const parsed = requestSchema.safeParse(payload);
10
- if (!parsed.success) {
11
- ctx.res.status(400);
12
- return { success: false, error: "invalid_payload" };
13
- }
14
- const { email_or_phone, password, remember_me: _remember_me } = parsed.data;
15
- const is_email = isEmail(email_or_phone);
16
- const query = is_email ? { email: email_or_phone } : { phone: email_or_phone };
17
- const existingUser = await User.findOne(query);
18
- if (existingUser) {
19
- console.log("user with email or phone already exists", email_or_phone);
20
- ctx.res.status(409);
21
- return { success: false, error: "user_exists" };
22
- }
23
- const salt = crypto.randomBytes(16).toString("hex");
24
- const derivedKey = await hashPassword(password, salt);
25
- const hashedPassword = `${salt}:${derivedKey.toString("hex")}`;
26
- const tenantId = crypto.randomUUID();
27
- const user = new User({
28
- ...query,
29
- password: hashedPassword,
30
- tenants: [tenantId]
31
- });
32
- await user.save();
33
- if (is_email) {
34
- try {
35
- await sendEmail({
36
- to: email_or_phone,
37
- subject: "Verify your email",
38
- html: "<p>Welcome to rpcbase!</p><p>We created your account. Use this email to sign in and finish verification.</p>",
39
- text: "Welcome to rpcbase! We created your account. Use this email to sign in and finish verification."
40
- });
41
- } catch (err) {
42
- console.warn("failed to send sign-up email", err);
43
- }
44
- }
45
- try {
46
- await Tenant.create({
47
- tenant_id: tenantId,
48
- name: query.email || query.phone
49
- });
50
- } catch (err) {
51
- console.warn("failed to create tenant for user", err);
52
- }
53
- console.log("created new user", user._id.toString());
54
- if (!ctx.req.session) {
55
- ctx.res.status(500);
56
- return { success: false, error: "session_unavailable" };
57
- }
58
- ctx.req.session.user = {
59
- id: user._id.toString(),
60
- current_tenant_id: tenantId,
61
- signed_in_tenants: [tenantId],
62
- is_entry_gate_authorized: true
63
- };
64
- return { success: true, user_id: user._id.toString(), tenant_id: tenantId };
65
- };
66
- const handler = (api) => {
67
- api.post(Route, signUp);
68
- };
69
- export {
70
- handler as default
71
- };
@@ -1,29 +0,0 @@
1
- import { i as isValidNumber } from "./isValidNumber-6pMDGLRn.js";
2
- import { o as object, b as boolean, s as string } from "./schemas-KL7REOdt.js";
3
- const Route = "/api/rb/auth/sign-up";
4
- const requestSchema = object({
5
- email_or_phone: string().nonempty("Email or phone number is required").refine(
6
- (value) => {
7
- const isEmail = string().email().safeParse(value).success;
8
- const isPhone = isValidNumber(value);
9
- return isEmail || isPhone;
10
- },
11
- "Please enter a valid email address or phone number"
12
- ),
13
- password: string().min(8, { message: "Password must be at least 8 characters long." }),
14
- password_confirmation: string().nonempty({ message: "Please confirm your password." }),
15
- remember_me: boolean().default(true)
16
- }).refine((data) => data.password === data.password_confirmation, {
17
- message: "Passwords do not match.",
18
- path: ["password_confirmation"]
19
- });
20
- object({
21
- success: boolean(),
22
- error: string().optional(),
23
- user_id: string().optional(),
24
- tenant_id: string().optional()
25
- });
26
- export {
27
- Route as R,
28
- requestSchema as r
29
- };