@rpcbase/auth 0.190.0 → 0.192.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":"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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/EmailInput/index.tsx"],"names":[],"mappings":"AAIA,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":"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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PasswordInput/index.tsx"],"names":[],"mappings":"AAMA,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"}
@@ -36,7 +36,7 @@ var requestPasswordReset = async (payload, ctx) => {
36
36
  };
37
37
  }
38
38
  const { email } = parsed.data;
39
- if (await (0, email_validation_exports.getEmailAddressValidationErrorCode)(email)) {
39
+ if (!await (0, email_validation_exports.isEmailAddressValid)(email)) {
40
40
  ctx.res.setHeader("Retry-After", String(60));
41
41
  return {
42
42
  success: true,
@@ -67,14 +67,14 @@ var requestPasswordReset = async (payload, ctx) => {
67
67
  console.info(`sending password reset account notice to email ${maskEmailForLog(email)}`);
68
68
  await sendEmail({
69
69
  to: email,
70
- subject: "No account found for your password reset request",
70
+ subject: "Password reset request",
71
71
  html: `
72
72
  <p>Hello,</p>
73
- <p>You requested a new password reset code for this email address, but unfortunately we couldn't find an account associated with it.</p>
73
+ <p>A password reset code has been requested for this email address, but there doesn't appear to be any account associated with it.</p>
74
74
  <p>If you didn't request this, you can ignore this message.</p>
75
75
  <p>If you're having trouble accessing your account, please contact support.</p>
76
76
  `,
77
- text: "Hello, You requested a new password reset code for this email address, but unfortunately we couldn't find an account associated with it. If you didn't request this, you can ignore this message. If you're having trouble accessing your account, please contact support."
77
+ text: "Hello, A password reset code has been requested for this email address, but there doesn't appear to be any account associated with it. If you didn't request this, you can ignore this message. If you're having trouble accessing your account, please contact support."
78
78
  });
79
79
  } catch (err) {
80
80
  console.error("failed to send password reset account notice email", err);
@@ -130,4 +130,4 @@ var handler_default = (api) => {
130
130
  //#endregion
131
131
  export { PASSWORD_RESET_COOLDOWN_SECONDS, handler_default as default, requestPasswordReset };
132
132
 
133
- //# sourceMappingURL=handler-DlaZThWe.js.map
133
+ //# sourceMappingURL=handler-BY6DP3QP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-BY6DP3QP.js","names":["z","ROUTE","requestSchema","object","email","string","RequestPayload","infer","responseSchema","success","boolean","error","optional","retryAfterSeconds","number","int","positive","ResponsePayload","crypto","Api","ApiHandler","Ctx","models","sendEmail","isEmailAddressValid","AuthSessionUser","RequestPasswordReset","PASSWORD_RESET_COOLDOWN_SECONDS","PASSWORD_RESET_CODE_TTL_SECONDS","maskEmailForLog","email","localRaw","domainRaw","split","local","domain","localPrefix","slice","Math","min","length","localMask","repeat","max","domainTailLength","domainTail","domainMask","requestPasswordReset","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","data","setHeader","String","retryAfterSeconds","User","getGlobal","requestedAt","Date","cooldownEligibleExpiresAt","getTime","passwordResetCode","randomInt","toString","padStart","passwordResetCodeExpiresAt","user","findOneAndUpdate","$or","$exists","$lte","$set","$unset","passwordResetToken","passwordResetTokenExpiresAt","returnDocument","existingUser","findOne","_id","console","info","to","subject","html","text","err","releaseResult","updateOne","modifiedCount","removeHeader","releaseErr","api","post","ROUTE"],"sources":["../src/api/request-password-reset/index.ts","../src/api/request-password-reset/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const ROUTE = \"/api/rb/auth/request-password-reset\"\n\nexport const requestSchema = z.object({\n email: z.string().email(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n retryAfterSeconds: z.number().int().positive().optional(),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n","import crypto from \"crypto\"\n\nimport { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { sendEmail } from \"@rpcbase/server\"\n\nimport { isEmailAddressValid } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as RequestPasswordReset from \"./index\"\n\n\nexport const PASSWORD_RESET_COOLDOWN_SECONDS = 60\nconst PASSWORD_RESET_CODE_TTL_SECONDS = 10 * 60\n\nconst maskEmailForLog = (email: string) => {\n const [localRaw, domainRaw = \"\"] = email.split(\"@\")\n const local = localRaw || \"\"\n const domain = domainRaw || \"\"\n const localPrefix = local.slice(0, Math.min(3, local.length))\n const localMask = \"*\".repeat(Math.max(5, local.length - localPrefix.length))\n\n if (!domain) {\n return `${localPrefix}${localMask}@****`\n }\n\n const domainTailLength = Math.min(7, domain.length)\n const domainTail = domain.slice(-domainTailLength)\n const domainMask = \"*\".repeat(Math.max(4, domain.length - domainTailLength))\n\n return `${localPrefix}${localMask}@${domainMask}${domainTail}`\n}\n\n\nexport const requestPasswordReset: ApiHandler<\n RequestPasswordReset.RequestPayload,\n RequestPasswordReset.ResponsePayload,\n AuthSessionUser\n> = async(\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<RequestPasswordReset.ResponsePayload> => {\n const parsed = RequestPasswordReset.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n const { email } = parsed.data\n\n if (!await isEmailAddressValid(email)) {\n ctx.res.setHeader(\"Retry-After\", String(PASSWORD_RESET_COOLDOWN_SECONDS))\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const requestedAt = new Date()\n const cooldownEligibleExpiresAt = new Date(\n requestedAt.getTime() + (PASSWORD_RESET_CODE_TTL_SECONDS - PASSWORD_RESET_COOLDOWN_SECONDS) * 1000,\n )\n const passwordResetCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const passwordResetCodeExpiresAt = new Date(requestedAt.getTime() + PASSWORD_RESET_CODE_TTL_SECONDS * 1000)\n\n const user = await User.findOneAndUpdate(\n {\n email,\n $or: [\n { passwordResetCodeExpiresAt: { $exists: false } },\n { passwordResetCodeExpiresAt: { $lte: cooldownEligibleExpiresAt } },\n ],\n },\n {\n $set: {\n passwordResetCode,\n passwordResetCodeExpiresAt,\n },\n $unset: {\n passwordResetToken: \"\",\n passwordResetTokenExpiresAt: \"\",\n },\n },\n { returnDocument: \"after\" },\n )\n\n ctx.res.setHeader(\"Retry-After\", String(PASSWORD_RESET_COOLDOWN_SECONDS))\n\n if (!user) {\n const existingUser = await User.findOne({ email }, { _id: 1 })\n\n if (!existingUser) {\n try {\n console.info(`sending password reset account notice to email ${maskEmailForLog(email)}`)\n await sendEmail({\n to: email,\n subject: \"Password reset request\",\n html: `\n <p>Hello,</p>\n <p>A password reset code has been requested for this email address, but there doesn't appear to be any account associated with it.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n <p>If you're having trouble accessing your account, please contact support.</p>\n `,\n text: \"Hello, A password reset code has been requested for this email address, but there doesn't appear to be any account associated with it. If you didn't request this, you can ignore this message. If you're having trouble accessing your account, please contact support.\",\n })\n } catch (err) {\n console.error(\"failed to send password reset account notice email\", err)\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n }\n\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n }\n\n try {\n console.info(`sending password reset to email ${maskEmailForLog(email)}`)\n await sendEmail({\n to: email,\n subject: `Your password reset code: ${passwordResetCode}`,\n html: `\n <p>Your password reset code is <strong>${passwordResetCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n text: `Your password reset code is ${passwordResetCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`,\n })\n } catch (err) {\n console.error(\"failed to send password reset email\", err)\n\n try {\n const releaseResult = await User.updateOne(\n { _id: user._id, passwordResetCode },\n {\n $unset: {\n passwordResetCode: \"\",\n passwordResetCodeExpiresAt: \"\",\n },\n },\n )\n if (releaseResult.modifiedCount > 0) {\n ctx.res.removeHeader(\"Retry-After\")\n }\n } catch (releaseErr) {\n console.error(\"failed to release password reset cooldown\", releaseErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(RequestPasswordReset.ROUTE, requestPasswordReset)\n}\n"],"mappings":"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"}
@@ -14,7 +14,7 @@ var signUp = async (payload, ctx) => {
14
14
  };
15
15
  }
16
16
  const { email, password, rememberMe: _rememberMe } = parsed.data;
17
- if (await (0, email_validation_exports.getEmailAddressValidationErrorCode)(email)) return { success: true };
17
+ if (!await (0, email_validation_exports.isEmailAddressValid)(email)) return { success: true };
18
18
  const User = await models.getGlobal("RBUser", ctx);
19
19
  const Tenant = await models.getGlobal("RBTenant", ctx);
20
20
  if (await User.findOne({ email })) {
@@ -89,4 +89,4 @@ var handler_default = (api) => {
89
89
  //#endregion
90
90
  export { handler_default as default, signUp };
91
91
 
92
- //# sourceMappingURL=handler-BbkBmi2V.js.map
92
+ //# sourceMappingURL=handler-DetFi4-9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-DetFi4-9.js","names":["crypto","Api","Ctx","ApiHandler","createProvisionedTenant","models","hashPasswordForStorage","sendEmail","isEmailAddressValid","AuthSessionUser","SignUp","signUp","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","email","password","rememberMe","_rememberMe","data","User","getGlobal","Tenant","existingUser","findOne","console","log","hashedPassword","tenantId","randomUUID","emailVerificationCode","randomInt","toString","padStart","emailVerificationExpiresAt","Date","now","user","tenants","tenantRoles","save","to","subject","html","text","err","deleteOne","_id","rollbackErr","name","api","post","ROUTE"],"sources":["../src/api/sign-up/handler.ts"],"sourcesContent":["import crypto from \"crypto\"\n\nimport { Api, Ctx, ApiHandler } from \"@rpcbase/api\"\nimport { createProvisionedTenant, models } from \"@rpcbase/db\"\nimport { hashPasswordForStorage, sendEmail } from \"@rpcbase/server\"\n\nimport { isEmailAddressValid } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as SignUp from \"./index\"\n\n\nexport const signUp: ApiHandler<SignUp.RequestPayload, SignUp.ResponsePayload, AuthSessionUser> = async(\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<SignUp.ResponsePayload> => {\n const parsed = SignUp.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { email, password, rememberMe: _rememberMe } = parsed.data\n\n if (!await isEmailAddressValid(email)) {\n return { success: true }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const Tenant = await models.getGlobal(\"RBTenant\", ctx)\n\n const existingUser = await User.findOne({ email })\n\n if (existingUser) {\n console.log(\"user with email already exists\", email)\n // For security, we don't want to reveal if an email is registered.\n // But for this implementation, we'll make it simple.\n ctx.res.status(409)\n return { success: false, error: \"user_exists\" }\n }\n\n const hashedPassword = await hashPasswordForStorage(password)\n\n const tenantId = crypto.randomUUID()\n\n const emailVerificationCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1000)\n\n const user = new User({\n email,\n password: hashedPassword,\n tenants: [tenantId],\n tenantRoles: {\n [tenantId]: [\"owner\"],\n },\n emailVerificationCode,\n emailVerificationExpiresAt,\n })\n await user.save()\n\n try {\n await sendEmail({\n to: email,\n subject: `Verify your email: ${emailVerificationCode}`,\n html: `\n <p>Welcome to rpcbase!</p>\n <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n 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.`,\n })\n } catch (err) {\n console.error(\"failed to send sign-up email\", err)\n\n try {\n await User.deleteOne({ _id: user._id })\n } catch (rollbackErr) {\n console.error(\"failed to roll back user after sign-up email failure\", rollbackErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n try {\n await createProvisionedTenant(Tenant, {\n tenantId,\n name: email,\n })\n } catch (err) {\n console.error(\"failed to initialize tenant for user\", err)\n try {\n await User.deleteOne({ _id: user._id })\n } catch (rollbackErr) {\n console.error(\"failed to roll back user after tenant initialization failure\", rollbackErr)\n }\n ctx.res.status(500)\n return { success: false, error: \"tenant_initialization_failed\" }\n }\n\n console.log(\"created new user\", user._id.toString())\n\n return { success: true }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(SignUp.ROUTE, signUp)\n}\n"],"mappings":";;;;;;AAYA,IAAaW,SAAqF,OAChGG,SACAC,QACoC;CACpC,MAAME,SAAAA,cAA8BE,UAAUL,OAAO;CAErD,IAAI,CAACG,OAAOG,SAAS;EACnBL,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAkB;CACpD;CAGA,MAAM,EAAEC,OAAOC,UAAUC,YAAYC,gBAAgBV,OAAOW;CAE5D,IAAI,CAAC,OAAA,GAAA,yBAAA,oBAAA,CAA0BJ,KAAK,GAClC,OAAO,EAAEJ,SAAS,KAAK;CAGzB,MAAMS,OAAO,MAAMxB,OAAOyB,UAAU,UAAUf,GAAG;CACjD,MAAMgB,SAAS,MAAM1B,OAAOyB,UAAU,YAAYf,GAAG;CAIrD,IAAIiB,MAFuBH,KAAKI,QAAQ,EAAET,MAAM,CAAC,GAE/B;EAChBU,QAAQC,IAAI,kCAAkCX,KAAK;EAGnDT,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAc;CAChD;CAEA,MAAMa,iBAAiB,MAAM9B,uBAAuBmB,QAAQ;CAE5D,MAAMY,WAAWrC,OAAOsC,WAAW;CAEnC,MAAMC,wBAAwBvC,OAAOwC,UAAU,GAAG,GAAS,CAAC,CAACC,SAAS,CAAC,CAACC,SAAS,GAAG,GAAG;CACvF,MAAMC,6BAA6B,IAAIC,KAAKA,KAAKC,IAAI,IAAI,MAAU,GAAI;CAEvE,MAAMC,OAAO,IAAIjB,KAAK;EACpBL;EACAC,UAAUW;EACVW,SAAS,CAACV,QAAQ;EAClBW,aAAa,GACVX,WAAW,CAAC,OAAO,EACtB;EACAE;EACAI;CACF,CAAC;CACD,MAAMG,KAAKG,KAAK;CAEhB,IAAI;EACF,MAAM1C,UAAU;GACd2C,IAAI1B;GACJ2B,SAAS,sBAAsBZ;GAC/Ba,MAAM;;+CAEmCb,sBAAqB;;;GAG9Dc,MAAM,iDAAiDd,sBAAqB;EAC9E,CAAC;CACH,SAASe,KAAK;EACZpB,QAAQX,MAAM,gCAAgC+B,GAAG;EAEjD,IAAI;GACF,MAAMzB,KAAK0B,UAAU,EAAEC,KAAKV,KAAKU,IAAI,CAAC;EACxC,SAASC,aAAa;GACpBvB,QAAQX,MAAM,wDAAwDkC,WAAW;EACnF;EAEA1C,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAwB;CAC1D;CAEA,IAAI;EACF,MAAMnB,wBAAwB2B,QAAQ;GACpCM;GACAqB,MAAMlC;EACR,CAAC;CACH,SAAS8B,KAAK;EACZpB,QAAQX,MAAM,wCAAwC+B,GAAG;EACzD,IAAI;GACF,MAAMzB,KAAK0B,UAAU,EAAEC,KAAKV,KAAKU,IAAI,CAAC;EACxC,SAASC,aAAa;GACpBvB,QAAQX,MAAM,gEAAgEkC,WAAW;EAC3F;EACA1C,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAA+B;CACjE;CAEAW,QAAQC,IAAI,oBAAoBW,KAAKU,IAAIf,SAAS,CAAC;CAEnD,OAAO,EAAErB,SAAS,KAAK;AACzB;AAEA,IAAA,mBAAgBuC,QAA8B;CAC5CA,IAAIC,KAAKlD,OAAcC,MAAM;AAC/B"}
@@ -22,7 +22,7 @@ var resendOtp = async (payload, ctx) => {
22
22
  };
23
23
  }
24
24
  const { email } = parsed.data;
25
- if (await (0, email_validation_exports.getEmailAddressValidationErrorCode)(email)) return { success: true };
25
+ if (!await (0, email_validation_exports.isEmailAddressValid)(email)) return { success: true };
26
26
  const User = await models.getGlobal("RBUser", ctx);
27
27
  const user = await User.findOne({ email });
28
28
  if (!user) {
@@ -74,4 +74,4 @@ var handler_default = (api) => {
74
74
  //#endregion
75
75
  export { handler_default as default, resendOtp };
76
76
 
77
- //# sourceMappingURL=handler-CjwUMkBv.js.map
77
+ //# sourceMappingURL=handler-Dw3o3smk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-Dw3o3smk.js","names":["z","ROUTE","requestSchema","object","email","string","RequestPayload","infer","responseSchema","success","boolean","error","optional","ResponsePayload","crypto","Api","ApiHandler","Ctx","models","sendEmail","isEmailAddressValid","AuthSessionUser","ResendOtp","resendOtp","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","email","data","User","getGlobal","user","findOne","emailVerificationCode","randomInt","toString","padStart","emailVerificationExpiresAt","Date","now","save","to","subject","html","text","err","console","updateOne","_id","$unset","releaseErr","api","post","ROUTE"],"sources":["../src/api/resend-otp/index.ts","../src/api/resend-otp/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const ROUTE = \"/api/rb/auth/resend-otp\"\n\nexport const requestSchema = z.object({\n email: z.string().email(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n\n","import crypto from \"crypto\"\n\nimport { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { sendEmail } from \"@rpcbase/server\"\n\nimport { isEmailAddressValid } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as ResendOtp from \"./index\"\n\n\nexport const resendOtp: ApiHandler<ResendOtp.RequestPayload, ResendOtp.ResponsePayload, AuthSessionUser> = async (\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<ResendOtp.ResponsePayload> => {\n const parsed = ResendOtp.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n const { email } = parsed.data\n\n if (!await isEmailAddressValid(email)) {\n return { success: true }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const user = await User.findOne({ email })\n\n if (!user) {\n // TODO(auth): avoid account enumeration (return a generic success response even when the user doesn't exist)\n ctx.res.status(404)\n return { success: false, error: \"user_not_found\" }\n }\n\n // TODO(auth): add server-side throttling / rate limiting for resend-otp\n const emailVerificationCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1000)\n\n user.emailVerificationCode = emailVerificationCode\n user.emailVerificationExpiresAt = emailVerificationExpiresAt\n await user.save()\n\n try {\n await sendEmail({\n to: email,\n subject: `Your verification code: ${emailVerificationCode}`,\n html: `\n <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n text: `Your verification code is ${emailVerificationCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`,\n })\n } catch (err) {\n console.error(\"failed to resend otp email\", err)\n\n try {\n await User.updateOne(\n { _id: user._id, emailVerificationCode },\n {\n $unset: {\n emailVerificationCode: \"\",\n emailVerificationExpiresAt: \"\",\n },\n },\n )\n } catch (releaseErr) {\n console.error(\"failed to clear unsent otp\", releaseErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n return { success: true }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(ResendOtp.ROUTE, resendOtp)\n}\n"],"mappings":";;;;;;AAGA,IAAaC,QAAQ;AAErB,IAAaC,gBAAgBF,OAAS,EACpCI,OAAOJ,OAAS,CAAC,CAACI,MAAM,EAC1B,CAAC;AAI6BJ,OAAS;CACrCS,SAAST,QAAU;CACnBW,OAAOX,OAAS,CAAC,CAACY,SAAS;AAC7B,CAAC;;;ACFD,IAAaW,YAA8F,OACzGG,SACAC,QACuC;CACvC,MAAME,SAAAA,cAAiCE,UAAUL,OAAO;CAExD,IAAI,CAACG,OAAOG,SAAS;EACnBL,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAkB;CACpD;CAEA,MAAM,EAAEC,UAAUP,OAAOQ;CAEzB,IAAI,CAAC,OAAA,GAAA,yBAAA,oBAAA,CAA0BD,KAAK,GAClC,OAAO,EAAEJ,SAAS,KAAK;CAGzB,MAAMM,OAAO,MAAMpB,OAAOqB,UAAU,UAAUZ,GAAG;CACjD,MAAMa,OAAO,MAAMF,KAAKG,QAAQ,EAAEL,MAAM,CAAC;CAEzC,IAAI,CAACI,MAAM;EAETb,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAiB;CACnD;CAGA,MAAMO,wBAAwB5B,OAAO6B,UAAU,GAAG,GAAS,CAAC,CAACC,SAAS,CAAC,CAACC,SAAS,GAAG,GAAG;CACvF,MAAMC,6BAA6B,IAAIC,KAAKA,KAAKC,IAAI,IAAI,MAAU,GAAI;CAEvER,KAAKE,wBAAwBA;CAC7BF,KAAKM,6BAA6BA;CAClC,MAAMN,KAAKS,KAAK;CAEhB,IAAI;EACF,MAAM9B,UAAU;GACd+B,IAAId;GACJe,SAAS,2BAA2BT;GACpCU,MAAM;+CACmCV,sBAAqB;;;GAG9DW,MAAM,6BAA6BX,sBAAqB;EAC1D,CAAC;CACH,SAASY,KAAK;EACZC,QAAQpB,MAAM,8BAA8BmB,GAAG;EAE/C,IAAI;GACF,MAAMhB,KAAKkB,UACT;IAAEC,KAAKjB,KAAKiB;IAAKf;GAAsB,GACvC,EACEgB,QAAQ;IACNhB,uBAAuB;IACvBI,4BAA4B;GAC9B,EACF,CACF;EACF,SAASa,YAAY;GACnBJ,QAAQpB,MAAM,8BAA8BwB,UAAU;EACxD;EAEAhC,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAwB;CAC1D;CAEA,OAAO,EAAEH,SAAS,KAAK;AACzB;AAEA,IAAA,mBAAgB4B,QAA8B;CAC5CA,IAAIC,KAAKvC,OAAiBC,SAAS;AACrC"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { Link, useLocation, useNavigate, useSearchParams } from "@rpcbase/router
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import clsx from "clsx";
9
9
  import { FormProvider, useForm, useFormContext, zodResolver } from "@rpcbase/form";
10
+ import { Input, cn } from "@rpcbase/ui";
10
11
  //#region src/components/AuthLayout/index.tsx
11
12
  var LINKS_REDIRECTION_MAP = {
12
13
  "/auth/sign-in": {
@@ -134,7 +135,7 @@ var EmailInput = (t0) => {
134
135
  } else t1 = $[1];
135
136
  let t2;
136
137
  if ($[2] !== className || $[3] !== id || $[4] !== placeholder || $[5] !== t1) {
137
- t2 = /* @__PURE__ */ jsx("input", {
138
+ t2 = /* @__PURE__ */ jsx(Input, {
138
139
  id,
139
140
  type: "email",
140
141
  required: true,
@@ -528,7 +529,7 @@ var Eye = createLucideIcon("eye", [["path", {
528
529
  //#endregion
529
530
  //#region src/components/PasswordInput/index.tsx
530
531
  var PasswordInput = (t0) => {
531
- const $ = c(24);
532
+ const $ = c(26);
532
533
  const { id, name: t1, className, placeholder, autoComplete: t2 } = t0;
533
534
  const name = t1 === void 0 ? "password" : t1;
534
535
  const autoComplete = t2 === void 0 ? "current-password" : t2;
@@ -536,92 +537,98 @@ var PasswordInput = (t0) => {
536
537
  const { register, formState } = useFormContext();
537
538
  const fieldError = formState.errors[name];
538
539
  const errorMessage = typeof fieldError === "string" ? fieldError : typeof fieldError?.message === "string" ? fieldError.message : void 0;
539
- const inputClassName = className ? `${className} pr-11` : "pr-11";
540
- const t3 = showPassword ? "text" : "password";
541
- const t4 = name;
542
- let t5;
543
- if ($[0] !== register || $[1] !== t4) {
544
- t5 = register(t4);
545
- $[0] = register;
546
- $[1] = t4;
547
- $[2] = t5;
548
- } else t5 = $[2];
540
+ let t3;
541
+ if ($[0] !== className) {
542
+ t3 = cn(className, "pr-11");
543
+ $[0] = className;
544
+ $[1] = t3;
545
+ } else t3 = $[1];
546
+ const inputClassName = t3;
547
+ const t4 = showPassword ? "text" : "password";
548
+ const t5 = name;
549
549
  let t6;
550
- if ($[3] !== autoComplete || $[4] !== id || $[5] !== inputClassName || $[6] !== placeholder || $[7] !== t3 || $[8] !== t5) {
551
- t6 = /* @__PURE__ */ jsx("input", {
550
+ if ($[2] !== register || $[3] !== t5) {
551
+ t6 = register(t5);
552
+ $[2] = register;
553
+ $[3] = t5;
554
+ $[4] = t6;
555
+ } else t6 = $[4];
556
+ let t7;
557
+ if ($[5] !== autoComplete || $[6] !== id || $[7] !== inputClassName || $[8] !== placeholder || $[9] !== t4 || $[10] !== t6) {
558
+ t7 = /* @__PURE__ */ jsx(Input, {
552
559
  id,
553
- type: t3,
560
+ type: t4,
554
561
  autoComplete,
555
562
  className: inputClassName,
556
563
  placeholder,
557
- ...t5
564
+ ...t6
558
565
  });
559
- $[3] = autoComplete;
560
- $[4] = id;
561
- $[5] = inputClassName;
562
- $[6] = placeholder;
563
- $[7] = t3;
564
- $[8] = t5;
565
- $[9] = t6;
566
- } else t6 = $[9];
567
- let t7;
568
- if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
569
- t7 = () => setShowPassword(_temp);
570
- $[10] = t7;
571
- } else t7 = $[10];
572
- const t8 = showPassword ? "Hide characters" : "Show characters";
573
- let t9;
574
- if ($[11] !== showPassword) {
575
- t9 = showPassword ? /* @__PURE__ */ jsx(EyeOff, {
566
+ $[5] = autoComplete;
567
+ $[6] = id;
568
+ $[7] = inputClassName;
569
+ $[8] = placeholder;
570
+ $[9] = t4;
571
+ $[10] = t6;
572
+ $[11] = t7;
573
+ } else t7 = $[11];
574
+ let t8;
575
+ if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
576
+ t8 = () => setShowPassword(_temp);
577
+ $[12] = t8;
578
+ } else t8 = $[12];
579
+ const t9 = showPassword ? "Hide characters" : "Show characters";
580
+ let t10;
581
+ if ($[13] !== showPassword) {
582
+ t10 = showPassword ? /* @__PURE__ */ jsx(EyeOff, {
576
583
  className: "h-5 w-5",
577
584
  "aria-hidden": true
578
585
  }) : /* @__PURE__ */ jsx(Eye, {
579
586
  className: "h-5 w-5",
580
587
  "aria-hidden": true
581
588
  });
582
- $[11] = showPassword;
583
- $[12] = t9;
584
- } else t9 = $[12];
585
- let t10;
586
- if ($[13] !== t8 || $[14] !== t9) {
587
- t10 = /* @__PURE__ */ jsx("button", {
589
+ $[13] = showPassword;
590
+ $[14] = t10;
591
+ } else t10 = $[14];
592
+ let t11;
593
+ if ($[15] !== t10 || $[16] !== t9) {
594
+ t11 = /* @__PURE__ */ jsx("button", {
588
595
  type: "button",
589
596
  className: "absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500",
590
- onClick: t7,
591
- "aria-label": t8,
592
- children: t9
597
+ onClick: t8,
598
+ "aria-label": t9,
599
+ children: t10
593
600
  });
594
- $[13] = t8;
595
- $[14] = t9;
596
601
  $[15] = t10;
597
- } else t10 = $[15];
598
- let t11;
599
- if ($[16] !== t10 || $[17] !== t6) {
600
- t11 = /* @__PURE__ */ jsxs("div", {
602
+ $[16] = t9;
603
+ $[17] = t11;
604
+ } else t11 = $[17];
605
+ let t12;
606
+ if ($[18] !== t11 || $[19] !== t7) {
607
+ t12 = /* @__PURE__ */ jsxs("div", {
601
608
  className: "relative",
602
- children: [t6, t10]
609
+ children: [t7, t11]
603
610
  });
604
- $[16] = t10;
605
- $[17] = t6;
606
611
  $[18] = t11;
607
- } else t11 = $[18];
608
- let t12;
609
- if ($[19] !== errorMessage) {
610
- t12 = errorMessage ? /* @__PURE__ */ jsx("p", {
611
- className: "mt-1 -mb-2 text-sm/6 text-red-500",
612
- children: errorMessage
613
- }) : null;
614
- $[19] = errorMessage;
612
+ $[19] = t7;
615
613
  $[20] = t12;
616
614
  } else t12 = $[20];
617
615
  let t13;
618
- if ($[21] !== t11 || $[22] !== t12) {
619
- t13 = /* @__PURE__ */ jsxs(Fragment, { children: [t11, t12] });
620
- $[21] = t11;
621
- $[22] = t12;
622
- $[23] = t13;
623
- } else t13 = $[23];
624
- return t13;
616
+ if ($[21] !== errorMessage) {
617
+ t13 = errorMessage ? /* @__PURE__ */ jsx("p", {
618
+ className: "mt-1 -mb-2 text-sm/6 text-red-500",
619
+ children: errorMessage
620
+ }) : null;
621
+ $[21] = errorMessage;
622
+ $[22] = t13;
623
+ } else t13 = $[22];
624
+ let t14;
625
+ if ($[23] !== t12 || $[24] !== t13) {
626
+ t14 = /* @__PURE__ */ jsxs(Fragment, { children: [t12, t13] });
627
+ $[23] = t12;
628
+ $[24] = t13;
629
+ $[25] = t14;
630
+ } else t14 = $[25];
631
+ return t14;
625
632
  };
626
633
  function _temp(prev) {
627
634
  return !prev;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["React","Link","useLocation","LINKS_REDIRECTION_MAP","Pathname","AuthLayout","t0","$","_c","sidePanel","t1","children","undefined","location","linkTitle","pathname","title","linkLocation","t2","t3","t4","t5","clsx","AppleSignInButton","t0","$","_c","onPress","className","t1","t2","t3","Symbol","for","t4","useFormContext","EmailInput","t0","$","_c","id","className","placeholder","register","formState","error","errors","email","errorMessage","message","t1","t2","t3","t4","hasUnsafeNextPathChars","value","i","length","code","charCodeAt","sanitizeNextPath","raw","nextPath","trim","startsWith","base","URL","url","origin","pathname","search","hash","ReactNode","useNavigate","useSearchParams","useForm","FormProvider","zodResolver","SubmitHandler","z","requestSchema","sanitizeNextPath","SignInFormValues","input","SignInForm","children","className","navigate","searchParams","methods","defaultValues","email","password","rememberMe","navigationGuard","resolver","onSubmit","data","clearErrors","res","fetch","method","headers","body","JSON","stringify","credentials","json","catch","success","error","ok","message","setError","type","nextPath","get","replace","err","handleSubmit","formState","errors","root","ReactNode","useState","useNavigate","useForm","FormProvider","zodResolver","SubmitHandler","z","requestSchema","SignUpFormValues","input","SignUpForm","children","className","otpNextPath","navigate","serverMessage","setServerMessage","methods","defaultValues","email","password","rememberMe","navigationGuard","resolver","onSubmit","data","clearErrors","res","fetch","method","headers","body","JSON","stringify","credentials","json","ok","message","error","setError","type","success","search","URLSearchParams","set","toString","err","handleSubmit","formState","errors","root","ElementType","useFormContext","RememberMeCheckbox","t0","$","_c","label","as","t1","Component","undefined","register","t2","t3","t4","t5","__iconNode","useState","Eye","EyeOff","useFormContext","PasswordInputProps","id","name","className","placeholder","autoComplete","PasswordInput","t0","$","_c","t1","t2","undefined","showPassword","setShowPassword","register","formState","fieldError","errors","errorMessage","message","inputClassName","t3","t4","Parameters","t5","t6","t7","Symbol","for","_temp","t8","t9","t10","t11","t12","t13","prev","useEffect","useState","clsx","useResendCountdown","t0","t1","$","_c","seconds","undefined","initiallyCountingDown","remaining","setRemaining","isCountingDown","setIsCountingDown","t2","t3","timer","setInterval","prev","clearInterval","t4","nextSeconds","value","restart","minutes","Math","floor","secs","t5","toString","padStart","formatted","canResend","t6","ResendCodeButton","onResend","className","disabled","Promise","isSending","setIsSending","handleClick","isDisabled","showCountdown","useRef","useState","useResendCountdown","DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS","PasswordResetCodeRequestResult","retryAfterSeconds","PasswordResetCodeRequestError","Error","errorCode","constructor","name","parseRetryAfterSeconds","value","seconds","Number","isFinite","undefined","Math","ceil","requestPasswordResetCode","email","Promise","response","fetch","method","headers","body","JSON","stringify","payload","json","catch","success","error","get","ok","usePasswordResetResend","cooldownSeconds","countdown","isResending","setIsResending","isResendingRef","resend","canResend","current","result","restart","remainingSeconds","remaining","formatted","isCountingDown","Boolean"],"sources":["../src/components/AuthLayout/index.tsx","../src/components/AppleSignInButton/index.tsx","../src/components/EmailInput/index.tsx","../src/utils/sanitizeNextPath.ts","../src/components/SignInForm/index.tsx","../src/components/SignUpForm/index.tsx","../src/components/RememberMeCheckbox/index.tsx","../../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs","../../../node_modules/lucide-react/dist/esm/defaultAttributes.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs","../../../node_modules/lucide-react/dist/esm/context.mjs","../../../node_modules/lucide-react/dist/esm/Icon.mjs","../../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs","../../../node_modules/lucide-react/dist/esm/icons/eye-off.mjs","../../../node_modules/lucide-react/dist/esm/icons/eye.mjs","../src/components/PasswordInput/index.tsx","../src/components/ResendCodeButton/index.tsx","../src/passwordReset.ts"],"sourcesContent":["import * as React from \"react\"\nimport {Link, useLocation } from \"@rpcbase/router\"\n\n\nconst LINKS_REDIRECTION_MAP = {\n \"/auth/sign-in\": {\n \"title\": \"Sign Up\",\n \"location\": \"/auth/sign-up\"\n },\n \"/auth/sign-up\": {\n \"title\": \"Sign In\",\n \"location\": \"/auth/sign-in\"\n },\n \"/auth/logout-success\": {\n \"title\": \"Sign In\",\n \"location\": \"/auth/sign-in\"\n }\n}\n\ntype Pathname = keyof typeof LINKS_REDIRECTION_MAP\n\nexport const AuthLayout = ({\n sidePanel = null,\n children,\n}: {\n sidePanel: React.ReactElement | null\n children: React.ReactNode\n}) => {\n const location = useLocation()\n\n const linkTitle = LINKS_REDIRECTION_MAP[location.pathname as Pathname]?.title\n const linkLocation = LINKS_REDIRECTION_MAP[location.pathname as Pathname]?.location\n\n return (\n <div className=\"container relative flex min-h-dvh flex-col items-center justify-center px-4 py-20 sm:px-6 md:px-8 lg:grid lg:h-dvh lg:w-full lg:max-w-none lg:grid-cols-2 lg:px-0 lg:py-0\">\n {/* Top-right redirection link */}\n {linkTitle && linkLocation ? (\n <Link\n className=\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 absolute right-4 top-4 md:right-8 md:top-8\"\n to={linkLocation}\n >\n {linkTitle}\n </Link>\n ) : null}\n <div className=\"relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex\">\n {sidePanel}\n </div>\n <div className=\"mx-auto flex w-full flex-col justify-center gap-6 /*sm:w-[350px]*/\">\n {children}\n </div>\n </div>\n )\n}\n","import clsx from \"clsx\"\n\n\nexport const AppleSignInButton = ({\n onPress,\n className,\n}: {\n onPress: () => void;\n className?: string;\n}) => {\n\n return (\n <button\n onClick={onPress}\n className={clsx(`\n w-full\n bg-black text-white hover:bg-gray-800\n flex items-center justify-center\n px-6 py-2\n rounded-lg\n font-medium\n transition duration-150 ease-in-out\n focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black\n `, className)}\n >\n <svg\n className=\"w-5 h-5 mr-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"white\"\n >\n <path d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.539 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\" />\n </svg>\n <span>Continue with Apple</span>\n </button>\n )\n}\n","import {useFormContext} from \"@rpcbase/form\"\n\n\nexport const EmailInput = ({\n id,\n className,\n placeholder,\n}: {\n id: string\n className?: string\n placeholder?: string\n}) => {\n const {register, formState} = useFormContext()\n\n const error = formState.errors.email\n let errorMessage: string | undefined\n if (typeof error === \"string\") {\n errorMessage = error\n } else if (error && typeof error.message === \"string\") {\n errorMessage = error.message\n }\n\n return (\n <>\n <input\n id={id}\n type=\"email\"\n required\n autoComplete=\"email\"\n className={className}\n placeholder={placeholder}\n {...register(\"email\")}\n />\n {errorMessage ? (\n <p className=\"mt-1 -mb-2 text-sm/6 text-red-500\">{errorMessage}</p>\n ) : null}\n </>\n )\n}\n","const hasUnsafeNextPathChars = (value: string) => {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code === 92 || code <= 31 || code === 127) return true\n }\n return false\n}\n\nexport const sanitizeNextPath = (raw: string | null) => {\n if (!raw) return null\n\n const nextPath = raw.trim()\n if (!nextPath) return null\n\n if (!nextPath.startsWith(\"/\") || nextPath.startsWith(\"//\")) {\n return null\n }\n\n if (hasUnsafeNextPathChars(nextPath)) {\n return null\n }\n\n try {\n const base = new URL(\"http://localhost\")\n const url = new URL(nextPath, base)\n if (url.origin !== base.origin) {\n return null\n }\n return `${url.pathname}${url.search}${url.hash}`\n } catch {\n return null\n }\n}\n\n","import { ReactNode } from \"react\"\nimport { useNavigate, useSearchParams } from \"@rpcbase/router\"\nimport { useForm, FormProvider, zodResolver } from \"@rpcbase/form\"\nimport type { SubmitHandler } from \"@rpcbase/form\"\nimport { z } from \"zod\"\n\nimport {requestSchema} from \"../../api/sign-in\"\nimport { sanitizeNextPath } from \"../../utils/sanitizeNextPath\"\n\n\ntype SignInFormValues = z.input<typeof requestSchema>\n\nexport const SignInForm = ({\n children,\n className\n}: {\n children: ReactNode,\n className?: string\n}) => {\n const navigate = useNavigate()\n const [searchParams] = useSearchParams()\n\n const methods = useForm<SignInFormValues>({\n defaultValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n navigationGuard: false,\n resolver: zodResolver(requestSchema)\n })\n\n const onSubmit: SubmitHandler<SignInFormValues> = async (data) => {\n methods.clearErrors(\"root\")\n\n try {\n const res = await fetch(\"/api/rb/auth/sign-in\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(data),\n credentials: \"include\",\n })\n\n const json = await res.json().catch(() => null) as { success?: boolean; error?: string } | null\n\n if (!res.ok || !json?.success) {\n const message = json?.error === \"invalid_credentials\"\n ? \"Invalid email or password.\"\n : \"Sign-in failed. Please try again.\"\n methods.setError(\"root\", { type: \"server\", message })\n return\n }\n\n const nextPath = sanitizeNextPath(searchParams.get(\"next\")) ?? \"/\"\n navigate(nextPath, { replace: true })\n } catch (err) {\n methods.setError(\"root\", { type: \"server\", message: \"Network error. Please try again.\" })\n }\n }\n\n return (\n <FormProvider {...methods}>\n <form method=\"post\" noValidate className={className} onSubmit={methods.handleSubmit(onSubmit)}>\n {children}\n {methods.formState.errors.root?.message && (\n <p className=\"mt-2 text-sm text-red-600\" role=\"alert\">\n {methods.formState.errors.root.message}\n </p>\n )}\n </form>\n </FormProvider>\n )\n}\n","import {ReactNode, useState} from \"react\"\nimport { useNavigate } from \"@rpcbase/router\"\nimport { useForm, FormProvider, zodResolver } from \"@rpcbase/form\"\nimport type {SubmitHandler} from \"@rpcbase/form\"\nimport { z } from \"zod\"\n\nimport {requestSchema} from \"../../api/sign-up\"\n\n\ntype SignUpFormValues = z.input<typeof requestSchema>\n\n\nexport const SignUpForm = ({\n children,\n className,\n otpNextPath\n}: {\n children: ReactNode,\n className?: string\n otpNextPath?: string\n}) => {\n\n const navigate = useNavigate()\n\n const [serverMessage, setServerMessage] = useState<string | null>(null)\n const methods = useForm<SignUpFormValues>({\n defaultValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n navigationGuard: false,\n resolver: zodResolver(requestSchema)\n })\n\n const onSubmit: SubmitHandler<SignUpFormValues> = async (data) => {\n setServerMessage(null)\n methods.clearErrors(\"root\")\n\n try {\n const res = await fetch(\"/api/rb/auth/sign-up\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(data),\n credentials: \"include\",\n })\n\n const json = await res.json()\n\n if (!res.ok) {\n const message = json.error === \"user_exists\"\n ? \"An account already exists with this email.\"\n : \"Sign-up failed. Please try again.\"\n methods.setError(\"root\", { type: \"server\", message })\n return\n }\n\n if (!json.success) {\n methods.setError(\"root\", { type: \"server\", message: \"Sign-up failed. Please try again.\" })\n return\n }\n\n const search = new URLSearchParams({ email: data.email })\n if (otpNextPath) {\n search.set(\"next\", otpNextPath)\n }\n navigate(`/auth/sign-up-otp?${search.toString()}`)\n setServerMessage(\"Account created. Check your inbox to verify your email.\")\n } catch (err) {\n methods.setError(\"root\", { type: \"server\", message: \"Network error. Please try again.\" })\n }\n }\n\n return (\n <FormProvider {...methods}>\n <form method=\"post\" noValidate className={className} onSubmit={methods.handleSubmit(onSubmit)}>\n {children}\n {methods.formState.errors.root?.message && (\n <p className=\"mt-2 text-sm text-red-600\" role=\"alert\">\n {methods.formState.errors.root.message}\n </p>\n )}\n {serverMessage && (\n <p className=\"mt-2 text-sm text-green-700\" role=\"status\">\n {serverMessage}\n </p>\n )}\n </form>\n </FormProvider>\n )\n}\n","import type { ElementType } from \"react\"\nimport {useFormContext} from \"@rpcbase/form\"\n\n\nexport const RememberMeCheckbox = ({\n label,\n as: Component = \"input\"\n}: {\n label: string,\n as?: ElementType\n}) => {\n const {register} = useFormContext()\n\n return (\n <>\n <Component\n id=\"rememberMe\"\n {...register(\"rememberMe\")}\n />\n <label\n htmlFor={\"rememberMe\"}\n className=\"pl-2 block text-sm/6 text-gray-900\"\n >\n {label}\n </label>\n </>\n )\n}\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst mergeClasses = (...classes) => classes.filter((className, index, array) => {\n return Boolean(className) && className.trim() !== \"\" && array.indexOf(className) === index;\n}).join(\" \").trim();\n\nexport { mergeClasses };\n//# sourceMappingURL=mergeClasses.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n\nexport { toKebabCase };\n//# sourceMappingURL=toKebabCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toCamelCase = (string) => string.replace(\n /^([A-Z])|[\\s-_]+(\\w)/g,\n (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()\n);\n\nexport { toCamelCase };\n//# sourceMappingURL=toCamelCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { toCamelCase } from './toCamelCase.mjs';\n\nconst toPascalCase = (string) => {\n const camelCase = toCamelCase(string);\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);\n};\n\nexport { toPascalCase };\n//# sourceMappingURL=toPascalCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nvar defaultAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n};\n\nexport { defaultAttributes as default };\n//# sourceMappingURL=defaultAttributes.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst hasA11yProp = (props) => {\n for (const prop in props) {\n if (prop.startsWith(\"aria-\") || prop === \"role\" || prop === \"title\") {\n return true;\n }\n }\n return false;\n};\n\nexport { hasA11yProp };\n//# sourceMappingURL=hasA11yProp.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { createContext, useContext, useMemo, createElement } from 'react';\n\nconst LucideContext = createContext({});\nfunction LucideProvider({\n children,\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n}) {\n const value = useMemo(\n () => ({\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n }),\n [size, color, strokeWidth, absoluteStrokeWidth, className]\n );\n return createElement(LucideContext.Provider, { value }, children);\n}\nconst useLucideContext = () => useContext(LucideContext);\n\nexport { LucideProvider, useLucideContext };\n//# sourceMappingURL=context.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes.mjs';\nimport { hasA11yProp } from './shared/src/utils/hasA11yProp.mjs';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { useLucideContext } from './context.mjs';\n\nconst Icon = forwardRef(\n ({ color, size, strokeWidth, absoluteStrokeWidth, className = \"\", children, iconNode, ...rest }, ref) => {\n const {\n size: contextSize = 24,\n strokeWidth: contextStrokeWidth = 2,\n absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,\n color: contextColor = \"currentColor\",\n className: contextClass = \"\"\n } = useLucideContext() ?? {};\n const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;\n return createElement(\n \"svg\",\n {\n ref,\n ...defaultAttributes,\n width: size ?? contextSize ?? defaultAttributes.width,\n height: size ?? contextSize ?? defaultAttributes.height,\n stroke: color ?? contextColor,\n strokeWidth: calculatedStrokeWidth,\n className: mergeClasses(\"lucide\", contextClass, className),\n ...!children && !hasA11yProp(rest) && { \"aria-hidden\": \"true\" },\n ...rest\n },\n [\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...Array.isArray(children) ? children : [children]\n ]\n );\n }\n);\n\nexport { Icon as default };\n//# sourceMappingURL=Icon.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { toKebabCase } from './shared/src/utils/toKebabCase.mjs';\nimport { toPascalCase } from './shared/src/utils/toPascalCase.mjs';\nimport Icon from './Icon.mjs';\n\nconst createLucideIcon = (iconName, iconNode) => {\n const Component = forwardRef(\n ({ className, ...props }, ref) => createElement(Icon, {\n ref,\n iconNode,\n className: mergeClasses(\n `lucide-${toKebabCase(toPascalCase(iconName))}`,\n `lucide-${iconName}`,\n className\n ),\n ...props\n })\n );\n Component.displayName = toPascalCase(iconName);\n return Component;\n};\n\nexport { createLucideIcon as default };\n//# sourceMappingURL=createLucideIcon.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\",\n key: \"ct8e1f\"\n }\n ],\n [\"path\", { d: \"M14.084 14.158a3 3 0 0 1-4.242-4.242\", key: \"151rxh\" }],\n [\n \"path\",\n {\n d: \"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\",\n key: \"13bj9a\"\n }\n ],\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }]\n];\nconst EyeOff = createLucideIcon(\"eye-off\", __iconNode);\n\nexport { __iconNode, EyeOff as default };\n//# sourceMappingURL=eye-off.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\",\n key: \"1nclc0\"\n }\n ],\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"3\", key: \"1v7zrd\" }]\n];\nconst Eye = createLucideIcon(\"eye\", __iconNode);\n\nexport { __iconNode, Eye as default };\n//# sourceMappingURL=eye.mjs.map\n","import { useState } from \"react\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { useFormContext } from \"@rpcbase/form\"\n\n\ntype PasswordInputProps = {\n id: string\n name?: string\n className?: string\n placeholder?: string\n autoComplete?: string\n}\n\nexport const PasswordInput = ({\n id,\n name = \"password\",\n className,\n placeholder,\n autoComplete = \"current-password\",\n}: PasswordInputProps) => {\n const [showPassword, setShowPassword] = useState(false)\n const { register, formState } = useFormContext()\n\n const fieldError = formState.errors[name as keyof typeof formState.errors]\n const errorMessage =\n typeof fieldError === \"string\"\n ? fieldError\n : typeof fieldError?.message === \"string\"\n ? fieldError.message\n : undefined\n\n const inputClassName = className ? `${className} pr-11` : \"pr-11\"\n\n return (\n <>\n <div className=\"relative\">\n <input\n id={id}\n type={showPassword ? \"text\" : \"password\"}\n autoComplete={autoComplete}\n className={inputClassName}\n placeholder={placeholder}\n {...register(name as Parameters<typeof register>[0])}\n />\n <button\n type=\"button\"\n className=\"absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500\"\n onClick={() => setShowPassword((prev) => !prev)}\n aria-label={showPassword ? \"Hide characters\" : \"Show characters\"}\n >\n {showPassword ? (\n <EyeOff className=\"h-5 w-5\" aria-hidden />\n ) : (\n <Eye className=\"h-5 w-5\" aria-hidden />\n )}\n </button>\n </div>\n {errorMessage ? (\n <p className=\"mt-1 -mb-2 text-sm/6 text-red-500\">{errorMessage}</p>\n ) : null}\n </>\n )\n}\n","import {useEffect, useState} from \"react\"\nimport clsx from \"clsx\"\n\n\nexport const useResendCountdown = (seconds = 60, initiallyCountingDown = true) => {\n const [remaining, setRemaining] = useState(seconds)\n const [isCountingDown, setIsCountingDown] = useState(initiallyCountingDown)\n\n useEffect(() => {\n if (!isCountingDown) return\n\n const timer = setInterval(() => {\n setRemaining((prev) => {\n if (prev <= 1) {\n setIsCountingDown(false)\n return seconds\n }\n return prev - 1\n })\n }, 1000)\n\n return () => clearInterval(timer)\n }, [isCountingDown, seconds])\n\n const restart = (nextSeconds?: number) => {\n const value = typeof nextSeconds === \"number\" ? nextSeconds : seconds\n setRemaining(value)\n setIsCountingDown(true)\n }\n\n const minutes = Math.floor(remaining / 60)\n const secs = remaining % 60\n const formatted = `${minutes}:${secs.toString().padStart(2, \"0\")}`\n\n const canResend = !isCountingDown\n\n return {remaining, formatted, isCountingDown, canResend, restart}\n}\n\n\nexport const ResendCodeButton = ({\n seconds = 60,\n onResend,\n className,\n disabled,\n}: {\n seconds?: number\n onResend?: () => Promise<void> | void\n className?: string\n disabled?: boolean\n}) => {\n const [isSending, setIsSending] = useState(false)\n const {formatted, isCountingDown, restart, canResend} = useResendCountdown(seconds)\n\n const handleClick = async () => {\n if (!canResend || isSending || disabled) return\n\n try {\n setIsSending(true)\n if (onResend) {\n await onResend()\n }\n } finally {\n restart()\n setIsSending(false)\n }\n }\n\n const isDisabled = disabled || isCountingDown || isSending\n const showCountdown = isDisabled && (isCountingDown || isSending)\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\n aria-label=\"Resend code\"\n className={clsx(\n \"text-sm font-semibold text-sky-600 hover:underline disabled:cursor-not-allowed disabled:text-gray-400\",\n className\n )}\n >\n Resend code\n </button>\n {showCountdown ? (\n <span\n data-testid=\"resend-countdown\"\n className=\"text-sm text-gray-500\"\n aria-live=\"polite\"\n >\n ({formatted})\n </span>\n ) : null}\n </span>\n )\n}\n","import { useRef, useState } from \"react\"\n\nimport { useResendCountdown } from \"./components/ResendCodeButton\"\n\n\nexport const DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS = 60\n\nexport type PasswordResetCodeRequestResult = {\n retryAfterSeconds: number\n}\n\nexport class PasswordResetCodeRequestError extends Error {\n retryAfterSeconds?: number\n errorCode?: string\n\n constructor(retryAfterSeconds?: number, errorCode?: string) {\n super(\"Unable to request a password reset code\")\n this.name = \"PasswordResetCodeRequestError\"\n this.retryAfterSeconds = retryAfterSeconds\n this.errorCode = errorCode\n }\n}\n\nconst parseRetryAfterSeconds = (value: unknown) => {\n const seconds = typeof value === \"number\" ? value : Number(value)\n if (!Number.isFinite(seconds) || seconds <= 0) return undefined\n return Math.ceil(seconds)\n}\n\nexport const requestPasswordResetCode = async(email: string): Promise<PasswordResetCodeRequestResult> => {\n const response = await fetch(\"/api/rb/auth/request-password-reset\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email }),\n })\n\n const payload = await response.json().catch(() => null) as {\n success?: unknown\n error?: unknown\n retryAfterSeconds?: unknown\n } | null\n const retryAfterSeconds = parseRetryAfterSeconds(payload?.retryAfterSeconds)\n ?? parseRetryAfterSeconds(response.headers.get(\"Retry-After\"))\n\n if (!response.ok || payload?.success !== true) {\n const errorCode = typeof payload?.error === \"string\" ? payload.error : undefined\n throw new PasswordResetCodeRequestError(retryAfterSeconds, errorCode)\n }\n\n return {\n retryAfterSeconds: retryAfterSeconds ?? DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS,\n }\n}\n\nexport const usePasswordResetResend = ({\n email,\n cooldownSeconds = DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS,\n}: {\n email?: string | null\n cooldownSeconds?: number\n}) => {\n const countdown = useResendCountdown(cooldownSeconds)\n const [isResending, setIsResending] = useState(false)\n const isResendingRef = useRef(false)\n\n const resend = async() => {\n if (!email || !countdown.canResend || isResendingRef.current) return null\n\n isResendingRef.current = true\n setIsResending(true)\n\n try {\n const result = await requestPasswordResetCode(email)\n countdown.restart(result.retryAfterSeconds)\n return result\n } catch (error) {\n if (error instanceof PasswordResetCodeRequestError && error.retryAfterSeconds) {\n countdown.restart(error.retryAfterSeconds)\n }\n throw error\n } finally {\n isResendingRef.current = false\n setIsResending(false)\n }\n }\n\n return {\n resend,\n isResending,\n remainingSeconds: countdown.remaining,\n formatted: countdown.formatted,\n isCountingDown: countdown.isCountingDown,\n canResend: Boolean(email) && countdown.canResend && !isResending,\n }\n}\n"],"x_google_ignoreList":[7,8,9,10,11,12,13,14,15,16,17],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["React","Link","useLocation","LINKS_REDIRECTION_MAP","Pathname","AuthLayout","t0","$","_c","sidePanel","t1","children","undefined","location","linkTitle","pathname","title","linkLocation","t2","t3","t4","t5","clsx","AppleSignInButton","t0","$","_c","onPress","className","t1","t2","t3","Symbol","for","t4","useFormContext","Input","EmailInput","t0","$","_c","id","className","placeholder","register","formState","error","errors","email","errorMessage","message","t1","t2","t3","t4","hasUnsafeNextPathChars","value","i","length","code","charCodeAt","sanitizeNextPath","raw","nextPath","trim","startsWith","base","URL","url","origin","pathname","search","hash","ReactNode","useNavigate","useSearchParams","useForm","FormProvider","zodResolver","SubmitHandler","z","requestSchema","sanitizeNextPath","SignInFormValues","input","SignInForm","children","className","navigate","searchParams","methods","defaultValues","email","password","rememberMe","navigationGuard","resolver","onSubmit","data","clearErrors","res","fetch","method","headers","body","JSON","stringify","credentials","json","catch","success","error","ok","message","setError","type","nextPath","get","replace","err","handleSubmit","formState","errors","root","ReactNode","useState","useNavigate","useForm","FormProvider","zodResolver","SubmitHandler","z","requestSchema","SignUpFormValues","input","SignUpForm","children","className","otpNextPath","navigate","serverMessage","setServerMessage","methods","defaultValues","email","password","rememberMe","navigationGuard","resolver","onSubmit","data","clearErrors","res","fetch","method","headers","body","JSON","stringify","credentials","json","ok","message","error","setError","type","success","search","URLSearchParams","set","toString","err","handleSubmit","formState","errors","root","ElementType","useFormContext","RememberMeCheckbox","t0","$","_c","label","as","t1","Component","undefined","register","t2","t3","t4","t5","__iconNode","useState","Eye","EyeOff","useFormContext","cn","Input","PasswordInputProps","id","name","className","placeholder","autoComplete","PasswordInput","t0","$","_c","t1","t2","undefined","showPassword","setShowPassword","register","formState","fieldError","errors","errorMessage","message","t3","inputClassName","t4","t5","Parameters","t6","t7","t8","Symbol","for","_temp","t9","t10","t11","t12","t13","t14","prev","useEffect","useState","clsx","useResendCountdown","t0","t1","$","_c","seconds","undefined","initiallyCountingDown","remaining","setRemaining","isCountingDown","setIsCountingDown","t2","t3","timer","setInterval","prev","clearInterval","t4","nextSeconds","value","restart","minutes","Math","floor","secs","t5","toString","padStart","formatted","canResend","t6","ResendCodeButton","onResend","className","disabled","Promise","isSending","setIsSending","handleClick","isDisabled","showCountdown","useRef","useState","useResendCountdown","DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS","PasswordResetCodeRequestResult","retryAfterSeconds","PasswordResetCodeRequestError","Error","errorCode","constructor","name","parseRetryAfterSeconds","value","seconds","Number","isFinite","undefined","Math","ceil","requestPasswordResetCode","email","Promise","response","fetch","method","headers","body","JSON","stringify","payload","json","catch","success","error","get","ok","usePasswordResetResend","cooldownSeconds","countdown","isResending","setIsResending","isResendingRef","resend","canResend","current","result","restart","remainingSeconds","remaining","formatted","isCountingDown","Boolean"],"sources":["../src/components/AuthLayout/index.tsx","../src/components/AppleSignInButton/index.tsx","../src/components/EmailInput/index.tsx","../src/utils/sanitizeNextPath.ts","../src/components/SignInForm/index.tsx","../src/components/SignUpForm/index.tsx","../src/components/RememberMeCheckbox/index.tsx","../../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs","../../../node_modules/lucide-react/dist/esm/defaultAttributes.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs","../../../node_modules/lucide-react/dist/esm/context.mjs","../../../node_modules/lucide-react/dist/esm/Icon.mjs","../../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs","../../../node_modules/lucide-react/dist/esm/icons/eye-off.mjs","../../../node_modules/lucide-react/dist/esm/icons/eye.mjs","../src/components/PasswordInput/index.tsx","../src/components/ResendCodeButton/index.tsx","../src/passwordReset.ts"],"sourcesContent":["import * as React from \"react\"\nimport {Link, useLocation } from \"@rpcbase/router\"\n\n\nconst LINKS_REDIRECTION_MAP = {\n \"/auth/sign-in\": {\n \"title\": \"Sign Up\",\n \"location\": \"/auth/sign-up\"\n },\n \"/auth/sign-up\": {\n \"title\": \"Sign In\",\n \"location\": \"/auth/sign-in\"\n },\n \"/auth/logout-success\": {\n \"title\": \"Sign In\",\n \"location\": \"/auth/sign-in\"\n }\n}\n\ntype Pathname = keyof typeof LINKS_REDIRECTION_MAP\n\nexport const AuthLayout = ({\n sidePanel = null,\n children,\n}: {\n sidePanel: React.ReactElement | null\n children: React.ReactNode\n}) => {\n const location = useLocation()\n\n const linkTitle = LINKS_REDIRECTION_MAP[location.pathname as Pathname]?.title\n const linkLocation = LINKS_REDIRECTION_MAP[location.pathname as Pathname]?.location\n\n return (\n <div className=\"container relative flex min-h-dvh flex-col items-center justify-center px-4 py-20 sm:px-6 md:px-8 lg:grid lg:h-dvh lg:w-full lg:max-w-none lg:grid-cols-2 lg:px-0 lg:py-0\">\n {/* Top-right redirection link */}\n {linkTitle && linkLocation ? (\n <Link\n className=\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 absolute right-4 top-4 md:right-8 md:top-8\"\n to={linkLocation}\n >\n {linkTitle}\n </Link>\n ) : null}\n <div className=\"relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex\">\n {sidePanel}\n </div>\n <div className=\"mx-auto flex w-full flex-col justify-center gap-6 /*sm:w-[350px]*/\">\n {children}\n </div>\n </div>\n )\n}\n","import clsx from \"clsx\"\n\n\nexport const AppleSignInButton = ({\n onPress,\n className,\n}: {\n onPress: () => void;\n className?: string;\n}) => {\n\n return (\n <button\n onClick={onPress}\n className={clsx(`\n w-full\n bg-black text-white hover:bg-gray-800\n flex items-center justify-center\n px-6 py-2\n rounded-lg\n font-medium\n transition duration-150 ease-in-out\n focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black\n `, className)}\n >\n <svg\n className=\"w-5 h-5 mr-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"white\"\n >\n <path d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.539 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\" />\n </svg>\n <span>Continue with Apple</span>\n </button>\n )\n}\n","import {useFormContext} from \"@rpcbase/form\"\nimport { Input } from \"@rpcbase/ui\"\n\n\nexport const EmailInput = ({\n id,\n className,\n placeholder,\n}: {\n id: string\n className?: string\n placeholder?: string\n}) => {\n const {register, formState} = useFormContext()\n\n const error = formState.errors.email\n let errorMessage: string | undefined\n if (typeof error === \"string\") {\n errorMessage = error\n } else if (error && typeof error.message === \"string\") {\n errorMessage = error.message\n }\n\n return (\n <>\n <Input\n id={id}\n type=\"email\"\n required\n autoComplete=\"email\"\n className={className}\n placeholder={placeholder}\n {...register(\"email\")}\n />\n {errorMessage ? (\n <p className=\"mt-1 -mb-2 text-sm/6 text-red-500\">{errorMessage}</p>\n ) : null}\n </>\n )\n}\n","const hasUnsafeNextPathChars = (value: string) => {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code === 92 || code <= 31 || code === 127) return true\n }\n return false\n}\n\nexport const sanitizeNextPath = (raw: string | null) => {\n if (!raw) return null\n\n const nextPath = raw.trim()\n if (!nextPath) return null\n\n if (!nextPath.startsWith(\"/\") || nextPath.startsWith(\"//\")) {\n return null\n }\n\n if (hasUnsafeNextPathChars(nextPath)) {\n return null\n }\n\n try {\n const base = new URL(\"http://localhost\")\n const url = new URL(nextPath, base)\n if (url.origin !== base.origin) {\n return null\n }\n return `${url.pathname}${url.search}${url.hash}`\n } catch {\n return null\n }\n}\n\n","import { ReactNode } from \"react\"\nimport { useNavigate, useSearchParams } from \"@rpcbase/router\"\nimport { useForm, FormProvider, zodResolver } from \"@rpcbase/form\"\nimport type { SubmitHandler } from \"@rpcbase/form\"\nimport { z } from \"zod\"\n\nimport {requestSchema} from \"../../api/sign-in\"\nimport { sanitizeNextPath } from \"../../utils/sanitizeNextPath\"\n\n\ntype SignInFormValues = z.input<typeof requestSchema>\n\nexport const SignInForm = ({\n children,\n className\n}: {\n children: ReactNode,\n className?: string\n}) => {\n const navigate = useNavigate()\n const [searchParams] = useSearchParams()\n\n const methods = useForm<SignInFormValues>({\n defaultValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n navigationGuard: false,\n resolver: zodResolver(requestSchema)\n })\n\n const onSubmit: SubmitHandler<SignInFormValues> = async (data) => {\n methods.clearErrors(\"root\")\n\n try {\n const res = await fetch(\"/api/rb/auth/sign-in\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(data),\n credentials: \"include\",\n })\n\n const json = await res.json().catch(() => null) as { success?: boolean; error?: string } | null\n\n if (!res.ok || !json?.success) {\n const message = json?.error === \"invalid_credentials\"\n ? \"Invalid email or password.\"\n : \"Sign-in failed. Please try again.\"\n methods.setError(\"root\", { type: \"server\", message })\n return\n }\n\n const nextPath = sanitizeNextPath(searchParams.get(\"next\")) ?? \"/\"\n navigate(nextPath, { replace: true })\n } catch (err) {\n methods.setError(\"root\", { type: \"server\", message: \"Network error. Please try again.\" })\n }\n }\n\n return (\n <FormProvider {...methods}>\n <form method=\"post\" noValidate className={className} onSubmit={methods.handleSubmit(onSubmit)}>\n {children}\n {methods.formState.errors.root?.message && (\n <p className=\"mt-2 text-sm text-red-600\" role=\"alert\">\n {methods.formState.errors.root.message}\n </p>\n )}\n </form>\n </FormProvider>\n )\n}\n","import {ReactNode, useState} from \"react\"\nimport { useNavigate } from \"@rpcbase/router\"\nimport { useForm, FormProvider, zodResolver } from \"@rpcbase/form\"\nimport type {SubmitHandler} from \"@rpcbase/form\"\nimport { z } from \"zod\"\n\nimport {requestSchema} from \"../../api/sign-up\"\n\n\ntype SignUpFormValues = z.input<typeof requestSchema>\n\n\nexport const SignUpForm = ({\n children,\n className,\n otpNextPath\n}: {\n children: ReactNode,\n className?: string\n otpNextPath?: string\n}) => {\n\n const navigate = useNavigate()\n\n const [serverMessage, setServerMessage] = useState<string | null>(null)\n const methods = useForm<SignUpFormValues>({\n defaultValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n navigationGuard: false,\n resolver: zodResolver(requestSchema)\n })\n\n const onSubmit: SubmitHandler<SignUpFormValues> = async (data) => {\n setServerMessage(null)\n methods.clearErrors(\"root\")\n\n try {\n const res = await fetch(\"/api/rb/auth/sign-up\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(data),\n credentials: \"include\",\n })\n\n const json = await res.json()\n\n if (!res.ok) {\n const message = json.error === \"user_exists\"\n ? \"An account already exists with this email.\"\n : \"Sign-up failed. Please try again.\"\n methods.setError(\"root\", { type: \"server\", message })\n return\n }\n\n if (!json.success) {\n methods.setError(\"root\", { type: \"server\", message: \"Sign-up failed. Please try again.\" })\n return\n }\n\n const search = new URLSearchParams({ email: data.email })\n if (otpNextPath) {\n search.set(\"next\", otpNextPath)\n }\n navigate(`/auth/sign-up-otp?${search.toString()}`)\n setServerMessage(\"Account created. Check your inbox to verify your email.\")\n } catch (err) {\n methods.setError(\"root\", { type: \"server\", message: \"Network error. Please try again.\" })\n }\n }\n\n return (\n <FormProvider {...methods}>\n <form method=\"post\" noValidate className={className} onSubmit={methods.handleSubmit(onSubmit)}>\n {children}\n {methods.formState.errors.root?.message && (\n <p className=\"mt-2 text-sm text-red-600\" role=\"alert\">\n {methods.formState.errors.root.message}\n </p>\n )}\n {serverMessage && (\n <p className=\"mt-2 text-sm text-green-700\" role=\"status\">\n {serverMessage}\n </p>\n )}\n </form>\n </FormProvider>\n )\n}\n","import type { ElementType } from \"react\"\nimport {useFormContext} from \"@rpcbase/form\"\n\n\nexport const RememberMeCheckbox = ({\n label,\n as: Component = \"input\"\n}: {\n label: string,\n as?: ElementType\n}) => {\n const {register} = useFormContext()\n\n return (\n <>\n <Component\n id=\"rememberMe\"\n {...register(\"rememberMe\")}\n />\n <label\n htmlFor={\"rememberMe\"}\n className=\"pl-2 block text-sm/6 text-gray-900\"\n >\n {label}\n </label>\n </>\n )\n}\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst mergeClasses = (...classes) => classes.filter((className, index, array) => {\n return Boolean(className) && className.trim() !== \"\" && array.indexOf(className) === index;\n}).join(\" \").trim();\n\nexport { mergeClasses };\n//# sourceMappingURL=mergeClasses.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n\nexport { toKebabCase };\n//# sourceMappingURL=toKebabCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toCamelCase = (string) => string.replace(\n /^([A-Z])|[\\s-_]+(\\w)/g,\n (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()\n);\n\nexport { toCamelCase };\n//# sourceMappingURL=toCamelCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { toCamelCase } from './toCamelCase.mjs';\n\nconst toPascalCase = (string) => {\n const camelCase = toCamelCase(string);\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);\n};\n\nexport { toPascalCase };\n//# sourceMappingURL=toPascalCase.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nvar defaultAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n};\n\nexport { defaultAttributes as default };\n//# sourceMappingURL=defaultAttributes.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst hasA11yProp = (props) => {\n for (const prop in props) {\n if (prop.startsWith(\"aria-\") || prop === \"role\" || prop === \"title\") {\n return true;\n }\n }\n return false;\n};\n\nexport { hasA11yProp };\n//# sourceMappingURL=hasA11yProp.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { createContext, useContext, useMemo, createElement } from 'react';\n\nconst LucideContext = createContext({});\nfunction LucideProvider({\n children,\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n}) {\n const value = useMemo(\n () => ({\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n }),\n [size, color, strokeWidth, absoluteStrokeWidth, className]\n );\n return createElement(LucideContext.Provider, { value }, children);\n}\nconst useLucideContext = () => useContext(LucideContext);\n\nexport { LucideProvider, useLucideContext };\n//# sourceMappingURL=context.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes.mjs';\nimport { hasA11yProp } from './shared/src/utils/hasA11yProp.mjs';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { useLucideContext } from './context.mjs';\n\nconst Icon = forwardRef(\n ({ color, size, strokeWidth, absoluteStrokeWidth, className = \"\", children, iconNode, ...rest }, ref) => {\n const {\n size: contextSize = 24,\n strokeWidth: contextStrokeWidth = 2,\n absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,\n color: contextColor = \"currentColor\",\n className: contextClass = \"\"\n } = useLucideContext() ?? {};\n const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;\n return createElement(\n \"svg\",\n {\n ref,\n ...defaultAttributes,\n width: size ?? contextSize ?? defaultAttributes.width,\n height: size ?? contextSize ?? defaultAttributes.height,\n stroke: color ?? contextColor,\n strokeWidth: calculatedStrokeWidth,\n className: mergeClasses(\"lucide\", contextClass, className),\n ...!children && !hasA11yProp(rest) && { \"aria-hidden\": \"true\" },\n ...rest\n },\n [\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...Array.isArray(children) ? children : [children]\n ]\n );\n }\n);\n\nexport { Icon as default };\n//# sourceMappingURL=Icon.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { toKebabCase } from './shared/src/utils/toKebabCase.mjs';\nimport { toPascalCase } from './shared/src/utils/toPascalCase.mjs';\nimport Icon from './Icon.mjs';\n\nconst createLucideIcon = (iconName, iconNode) => {\n const Component = forwardRef(\n ({ className, ...props }, ref) => createElement(Icon, {\n ref,\n iconNode,\n className: mergeClasses(\n `lucide-${toKebabCase(toPascalCase(iconName))}`,\n `lucide-${iconName}`,\n className\n ),\n ...props\n })\n );\n Component.displayName = toPascalCase(iconName);\n return Component;\n};\n\nexport { createLucideIcon as default };\n//# sourceMappingURL=createLucideIcon.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\",\n key: \"ct8e1f\"\n }\n ],\n [\"path\", { d: \"M14.084 14.158a3 3 0 0 1-4.242-4.242\", key: \"151rxh\" }],\n [\n \"path\",\n {\n d: \"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\",\n key: \"13bj9a\"\n }\n ],\n [\"path\", { d: \"m2 2 20 20\", key: \"1ooewy\" }]\n];\nconst EyeOff = createLucideIcon(\"eye-off\", __iconNode);\n\nexport { __iconNode, EyeOff as default };\n//# sourceMappingURL=eye-off.mjs.map\n","/**\n * @license lucide-react v1.22.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\",\n key: \"1nclc0\"\n }\n ],\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"3\", key: \"1v7zrd\" }]\n];\nconst Eye = createLucideIcon(\"eye\", __iconNode);\n\nexport { __iconNode, Eye as default };\n//# sourceMappingURL=eye.mjs.map\n","import { useState } from \"react\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { useFormContext } from \"@rpcbase/form\"\nimport { cn, Input } from \"@rpcbase/ui\"\n\n\ntype PasswordInputProps = {\n id: string\n name?: string\n className?: string\n placeholder?: string\n autoComplete?: string\n}\n\nexport const PasswordInput = ({\n id,\n name = \"password\",\n className,\n placeholder,\n autoComplete = \"current-password\",\n}: PasswordInputProps) => {\n const [showPassword, setShowPassword] = useState(false)\n const { register, formState } = useFormContext()\n\n const fieldError = formState.errors[name as keyof typeof formState.errors]\n const errorMessage =\n typeof fieldError === \"string\"\n ? fieldError\n : typeof fieldError?.message === \"string\"\n ? fieldError.message\n : undefined\n\n const inputClassName = cn(className, \"pr-11\")\n\n return (\n <>\n <div className=\"relative\">\n <Input\n id={id}\n type={showPassword ? \"text\" : \"password\"}\n autoComplete={autoComplete}\n className={inputClassName}\n placeholder={placeholder}\n {...register(name as Parameters<typeof register>[0])}\n />\n <button\n type=\"button\"\n className=\"absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500\"\n onClick={() => setShowPassword((prev) => !prev)}\n aria-label={showPassword ? \"Hide characters\" : \"Show characters\"}\n >\n {showPassword ? (\n <EyeOff className=\"h-5 w-5\" aria-hidden />\n ) : (\n <Eye className=\"h-5 w-5\" aria-hidden />\n )}\n </button>\n </div>\n {errorMessage ? (\n <p className=\"mt-1 -mb-2 text-sm/6 text-red-500\">{errorMessage}</p>\n ) : null}\n </>\n )\n}\n","import {useEffect, useState} from \"react\"\nimport clsx from \"clsx\"\n\n\nexport const useResendCountdown = (seconds = 60, initiallyCountingDown = true) => {\n const [remaining, setRemaining] = useState(seconds)\n const [isCountingDown, setIsCountingDown] = useState(initiallyCountingDown)\n\n useEffect(() => {\n if (!isCountingDown) return\n\n const timer = setInterval(() => {\n setRemaining((prev) => {\n if (prev <= 1) {\n setIsCountingDown(false)\n return seconds\n }\n return prev - 1\n })\n }, 1000)\n\n return () => clearInterval(timer)\n }, [isCountingDown, seconds])\n\n const restart = (nextSeconds?: number) => {\n const value = typeof nextSeconds === \"number\" ? nextSeconds : seconds\n setRemaining(value)\n setIsCountingDown(true)\n }\n\n const minutes = Math.floor(remaining / 60)\n const secs = remaining % 60\n const formatted = `${minutes}:${secs.toString().padStart(2, \"0\")}`\n\n const canResend = !isCountingDown\n\n return {remaining, formatted, isCountingDown, canResend, restart}\n}\n\n\nexport const ResendCodeButton = ({\n seconds = 60,\n onResend,\n className,\n disabled,\n}: {\n seconds?: number\n onResend?: () => Promise<void> | void\n className?: string\n disabled?: boolean\n}) => {\n const [isSending, setIsSending] = useState(false)\n const {formatted, isCountingDown, restart, canResend} = useResendCountdown(seconds)\n\n const handleClick = async () => {\n if (!canResend || isSending || disabled) return\n\n try {\n setIsSending(true)\n if (onResend) {\n await onResend()\n }\n } finally {\n restart()\n setIsSending(false)\n }\n }\n\n const isDisabled = disabled || isCountingDown || isSending\n const showCountdown = isDisabled && (isCountingDown || isSending)\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\n aria-label=\"Resend code\"\n className={clsx(\n \"text-sm font-semibold text-sky-600 hover:underline disabled:cursor-not-allowed disabled:text-gray-400\",\n className\n )}\n >\n Resend code\n </button>\n {showCountdown ? (\n <span\n data-testid=\"resend-countdown\"\n className=\"text-sm text-gray-500\"\n aria-live=\"polite\"\n >\n ({formatted})\n </span>\n ) : null}\n </span>\n )\n}\n","import { useRef, useState } from \"react\"\n\nimport { useResendCountdown } from \"./components/ResendCodeButton\"\n\n\nexport const DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS = 60\n\nexport type PasswordResetCodeRequestResult = {\n retryAfterSeconds: number\n}\n\nexport class PasswordResetCodeRequestError extends Error {\n retryAfterSeconds?: number\n errorCode?: string\n\n constructor(retryAfterSeconds?: number, errorCode?: string) {\n super(\"Unable to request a password reset code\")\n this.name = \"PasswordResetCodeRequestError\"\n this.retryAfterSeconds = retryAfterSeconds\n this.errorCode = errorCode\n }\n}\n\nconst parseRetryAfterSeconds = (value: unknown) => {\n const seconds = typeof value === \"number\" ? value : Number(value)\n if (!Number.isFinite(seconds) || seconds <= 0) return undefined\n return Math.ceil(seconds)\n}\n\nexport const requestPasswordResetCode = async(email: string): Promise<PasswordResetCodeRequestResult> => {\n const response = await fetch(\"/api/rb/auth/request-password-reset\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email }),\n })\n\n const payload = await response.json().catch(() => null) as {\n success?: unknown\n error?: unknown\n retryAfterSeconds?: unknown\n } | null\n const retryAfterSeconds = parseRetryAfterSeconds(payload?.retryAfterSeconds)\n ?? parseRetryAfterSeconds(response.headers.get(\"Retry-After\"))\n\n if (!response.ok || payload?.success !== true) {\n const errorCode = typeof payload?.error === \"string\" ? payload.error : undefined\n throw new PasswordResetCodeRequestError(retryAfterSeconds, errorCode)\n }\n\n return {\n retryAfterSeconds: retryAfterSeconds ?? DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS,\n }\n}\n\nexport const usePasswordResetResend = ({\n email,\n cooldownSeconds = DEFAULT_PASSWORD_RESET_COOLDOWN_SECONDS,\n}: {\n email?: string | null\n cooldownSeconds?: number\n}) => {\n const countdown = useResendCountdown(cooldownSeconds)\n const [isResending, setIsResending] = useState(false)\n const isResendingRef = useRef(false)\n\n const resend = async() => {\n if (!email || !countdown.canResend || isResendingRef.current) return null\n\n isResendingRef.current = true\n setIsResending(true)\n\n try {\n const result = await requestPasswordResetCode(email)\n countdown.restart(result.retryAfterSeconds)\n return result\n } catch (error) {\n if (error instanceof PasswordResetCodeRequestError && error.retryAfterSeconds) {\n countdown.restart(error.retryAfterSeconds)\n }\n throw error\n } finally {\n isResendingRef.current = false\n setIsResending(false)\n }\n }\n\n return {\n resend,\n isResending,\n remainingSeconds: countdown.remaining,\n formatted: countdown.formatted,\n isCountingDown: countdown.isCountingDown,\n canResend: Boolean(email) && countdown.canResend && !isResending,\n }\n}\n"],"x_google_ignoreList":[7,8,9,10,11,12,13,14,15,16,17],"mappings":"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"}
package/dist/routes.js CHANGED
@@ -1,13 +1,13 @@
1
1
  //#region src/routes.ts
2
2
  var routes = Object.entries({ .../* #__PURE__ */ Object.assign({
3
3
  "./api/me/handler.ts": () => import("./handler-W_XZ0lnx.js"),
4
- "./api/request-password-reset/handler.ts": () => import("./handler-DlaZThWe.js"),
5
- "./api/resend-otp/handler.ts": () => import("./handler-CjwUMkBv.js"),
4
+ "./api/request-password-reset/handler.ts": () => import("./handler-BY6DP3QP.js"),
5
+ "./api/resend-otp/handler.ts": () => import("./handler-Dw3o3smk.js"),
6
6
  "./api/set-active-account/handler.ts": () => import("./handler-J2FOxq-2.js"),
7
7
  "./api/set-new-password/handler.ts": () => import("./handler-DoyR_l2O.js"),
8
8
  "./api/sign-in/handler.ts": () => import("./handler-DH9FPTNh.js"),
9
9
  "./api/sign-out/handler.ts": () => import("./handler-DQvdRpG2.js"),
10
- "./api/sign-up/handler.ts": () => import("./handler-BbkBmi2V.js"),
10
+ "./api/sign-up/handler.ts": () => import("./handler-DetFi4-9.js"),
11
11
  "./api/verify-otp/handler.ts": () => import("./handler-DAKI9QOQ.js"),
12
12
  "./api/verify-password-reset-otp/handler.ts": () => import("./handler-DAfIloZR.js")
13
13
  }) }).reduce((acc, [path, mod]) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/auth",
3
- "version": "0.190.0",
3
+ "version": "0.192.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -44,7 +44,8 @@
44
44
  "../db:build",
45
45
  "../form:build",
46
46
  "../router:build",
47
- "../server:build"
47
+ "../server:build",
48
+ "../ui:build"
48
49
  ],
49
50
  "files": [
50
51
  "src/**/*",
@@ -102,6 +103,7 @@
102
103
  "@rpcbase/form": "*",
103
104
  "@rpcbase/router": "*",
104
105
  "@rpcbase/server": "*",
106
+ "@rpcbase/ui": "*",
105
107
  "clsx": "*"
106
108
  }
107
109
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-BbkBmi2V.js","names":["crypto","Api","Ctx","ApiHandler","createProvisionedTenant","models","hashPasswordForStorage","sendEmail","getEmailAddressValidationErrorCode","AuthSessionUser","SignUp","signUp","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","email","password","rememberMe","_rememberMe","data","User","getGlobal","Tenant","existingUser","findOne","console","log","hashedPassword","tenantId","randomUUID","emailVerificationCode","randomInt","toString","padStart","emailVerificationExpiresAt","Date","now","user","tenants","tenantRoles","save","to","subject","html","text","err","deleteOne","_id","rollbackErr","name","api","post","ROUTE"],"sources":["../src/api/sign-up/handler.ts"],"sourcesContent":["import crypto from \"crypto\"\n\nimport { Api, Ctx, ApiHandler } from \"@rpcbase/api\"\nimport { createProvisionedTenant, models } from \"@rpcbase/db\"\nimport { hashPasswordForStorage, sendEmail } from \"@rpcbase/server\"\n\nimport { getEmailAddressValidationErrorCode } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as SignUp from \"./index\"\n\n\nexport const signUp: ApiHandler<SignUp.RequestPayload, SignUp.ResponsePayload, AuthSessionUser> = async(\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<SignUp.ResponsePayload> => {\n const parsed = SignUp.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { email, password, rememberMe: _rememberMe } = parsed.data\n\n if (await getEmailAddressValidationErrorCode(email)) {\n return { success: true }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const Tenant = await models.getGlobal(\"RBTenant\", ctx)\n\n const existingUser = await User.findOne({ email })\n\n if (existingUser) {\n console.log(\"user with email already exists\", email)\n // For security, we don't want to reveal if an email is registered.\n // But for this implementation, we'll make it simple.\n ctx.res.status(409)\n return { success: false, error: \"user_exists\" }\n }\n\n const hashedPassword = await hashPasswordForStorage(password)\n\n const tenantId = crypto.randomUUID()\n\n const emailVerificationCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1000)\n\n const user = new User({\n email,\n password: hashedPassword,\n tenants: [tenantId],\n tenantRoles: {\n [tenantId]: [\"owner\"],\n },\n emailVerificationCode,\n emailVerificationExpiresAt,\n })\n await user.save()\n\n try {\n await sendEmail({\n to: email,\n subject: `Verify your email: ${emailVerificationCode}`,\n html: `\n <p>Welcome to rpcbase!</p>\n <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n 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.`,\n })\n } catch (err) {\n console.error(\"failed to send sign-up email\", err)\n\n try {\n await User.deleteOne({ _id: user._id })\n } catch (rollbackErr) {\n console.error(\"failed to roll back user after sign-up email failure\", rollbackErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n try {\n await createProvisionedTenant(Tenant, {\n tenantId,\n name: email,\n })\n } catch (err) {\n console.error(\"failed to initialize tenant for user\", err)\n try {\n await User.deleteOne({ _id: user._id })\n } catch (rollbackErr) {\n console.error(\"failed to roll back user after tenant initialization failure\", rollbackErr)\n }\n ctx.res.status(500)\n return { success: false, error: \"tenant_initialization_failed\" }\n }\n\n console.log(\"created new user\", user._id.toString())\n\n return { success: true }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(SignUp.ROUTE, signUp)\n}\n"],"mappings":";;;;;;AAYA,IAAaW,SAAqF,OAChGG,SACAC,QACoC;CACpC,MAAME,SAAAA,cAA8BE,UAAUL,OAAO;CAErD,IAAI,CAACG,OAAOG,SAAS;EACnBL,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAkB;CACpD;CAGA,MAAM,EAAEC,OAAOC,UAAUC,YAAYC,gBAAgBV,OAAOW;CAE5D,IAAI,OAAA,GAAA,yBAAA,mCAAA,CAAyCJ,KAAK,GAChD,OAAO,EAAEJ,SAAS,KAAK;CAGzB,MAAMS,OAAO,MAAMxB,OAAOyB,UAAU,UAAUf,GAAG;CACjD,MAAMgB,SAAS,MAAM1B,OAAOyB,UAAU,YAAYf,GAAG;CAIrD,IAAIiB,MAFuBH,KAAKI,QAAQ,EAAET,MAAM,CAAC,GAE/B;EAChBU,QAAQC,IAAI,kCAAkCX,KAAK;EAGnDT,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAc;CAChD;CAEA,MAAMa,iBAAiB,MAAM9B,uBAAuBmB,QAAQ;CAE5D,MAAMY,WAAWrC,OAAOsC,WAAW;CAEnC,MAAMC,wBAAwBvC,OAAOwC,UAAU,GAAG,GAAS,CAAC,CAACC,SAAS,CAAC,CAACC,SAAS,GAAG,GAAG;CACvF,MAAMC,6BAA6B,IAAIC,KAAKA,KAAKC,IAAI,IAAI,MAAU,GAAI;CAEvE,MAAMC,OAAO,IAAIjB,KAAK;EACpBL;EACAC,UAAUW;EACVW,SAAS,CAACV,QAAQ;EAClBW,aAAa,GACVX,WAAW,CAAC,OAAO,EACtB;EACAE;EACAI;CACF,CAAC;CACD,MAAMG,KAAKG,KAAK;CAEhB,IAAI;EACF,MAAM1C,UAAU;GACd2C,IAAI1B;GACJ2B,SAAS,sBAAsBZ;GAC/Ba,MAAM;;+CAEmCb,sBAAqB;;;GAG9Dc,MAAM,iDAAiDd,sBAAqB;EAC9E,CAAC;CACH,SAASe,KAAK;EACZpB,QAAQX,MAAM,gCAAgC+B,GAAG;EAEjD,IAAI;GACF,MAAMzB,KAAK0B,UAAU,EAAEC,KAAKV,KAAKU,IAAI,CAAC;EACxC,SAASC,aAAa;GACpBvB,QAAQX,MAAM,wDAAwDkC,WAAW;EACnF;EAEA1C,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAwB;CAC1D;CAEA,IAAI;EACF,MAAMnB,wBAAwB2B,QAAQ;GACpCM;GACAqB,MAAMlC;EACR,CAAC;CACH,SAAS8B,KAAK;EACZpB,QAAQX,MAAM,wCAAwC+B,GAAG;EACzD,IAAI;GACF,MAAMzB,KAAK0B,UAAU,EAAEC,KAAKV,KAAKU,IAAI,CAAC;EACxC,SAASC,aAAa;GACpBvB,QAAQX,MAAM,gEAAgEkC,WAAW;EAC3F;EACA1C,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAA+B;CACjE;CAEAW,QAAQC,IAAI,oBAAoBW,KAAKU,IAAIf,SAAS,CAAC;CAEnD,OAAO,EAAErB,SAAS,KAAK;AACzB;AAEA,IAAA,mBAAgBuC,QAA8B;CAC5CA,IAAIC,KAAKlD,OAAcC,MAAM;AAC/B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-CjwUMkBv.js","names":["z","ROUTE","requestSchema","object","email","string","RequestPayload","infer","responseSchema","success","boolean","error","optional","ResponsePayload","crypto","Api","ApiHandler","Ctx","models","sendEmail","getEmailAddressValidationErrorCode","AuthSessionUser","ResendOtp","resendOtp","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","email","data","User","getGlobal","user","findOne","emailVerificationCode","randomInt","toString","padStart","emailVerificationExpiresAt","Date","now","save","to","subject","html","text","err","console","updateOne","_id","$unset","releaseErr","api","post","ROUTE"],"sources":["../src/api/resend-otp/index.ts","../src/api/resend-otp/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const ROUTE = \"/api/rb/auth/resend-otp\"\n\nexport const requestSchema = z.object({\n email: z.string().email(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n\n","import crypto from \"crypto\"\n\nimport { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { sendEmail } from \"@rpcbase/server\"\n\nimport { getEmailAddressValidationErrorCode } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as ResendOtp from \"./index\"\n\n\nexport const resendOtp: ApiHandler<ResendOtp.RequestPayload, ResendOtp.ResponsePayload, AuthSessionUser> = async (\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<ResendOtp.ResponsePayload> => {\n const parsed = ResendOtp.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n const { email } = parsed.data\n\n if (await getEmailAddressValidationErrorCode(email)) {\n return { success: true }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const user = await User.findOne({ email })\n\n if (!user) {\n // TODO(auth): avoid account enumeration (return a generic success response even when the user doesn't exist)\n ctx.res.status(404)\n return { success: false, error: \"user_not_found\" }\n }\n\n // TODO(auth): add server-side throttling / rate limiting for resend-otp\n const emailVerificationCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const emailVerificationExpiresAt = new Date(Date.now() + 10 * 60 * 1000)\n\n user.emailVerificationCode = emailVerificationCode\n user.emailVerificationExpiresAt = emailVerificationExpiresAt\n await user.save()\n\n try {\n await sendEmail({\n to: email,\n subject: `Your verification code: ${emailVerificationCode}`,\n html: `\n <p>Your verification code is <strong>${emailVerificationCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n text: `Your verification code is ${emailVerificationCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`,\n })\n } catch (err) {\n console.error(\"failed to resend otp email\", err)\n\n try {\n await User.updateOne(\n { _id: user._id, emailVerificationCode },\n {\n $unset: {\n emailVerificationCode: \"\",\n emailVerificationExpiresAt: \"\",\n },\n },\n )\n } catch (releaseErr) {\n console.error(\"failed to clear unsent otp\", releaseErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n return { success: true }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(ResendOtp.ROUTE, resendOtp)\n}\n"],"mappings":";;;;;;AAGA,IAAaC,QAAQ;AAErB,IAAaC,gBAAgBF,OAAS,EACpCI,OAAOJ,OAAS,CAAC,CAACI,MAAM,EAC1B,CAAC;AAI6BJ,OAAS;CACrCS,SAAST,QAAU;CACnBW,OAAOX,OAAS,CAAC,CAACY,SAAS;AAC7B,CAAC;;;ACFD,IAAaW,YAA8F,OACzGG,SACAC,QACuC;CACvC,MAAME,SAAAA,cAAiCE,UAAUL,OAAO;CAExD,IAAI,CAACG,OAAOG,SAAS;EACnBL,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAkB;CACpD;CAEA,MAAM,EAAEC,UAAUP,OAAOQ;CAEzB,IAAI,OAAA,GAAA,yBAAA,mCAAA,CAAyCD,KAAK,GAChD,OAAO,EAAEJ,SAAS,KAAK;CAGzB,MAAMM,OAAO,MAAMpB,OAAOqB,UAAU,UAAUZ,GAAG;CACjD,MAAMa,OAAO,MAAMF,KAAKG,QAAQ,EAAEL,MAAM,CAAC;CAEzC,IAAI,CAACI,MAAM;EAETb,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAiB;CACnD;CAGA,MAAMO,wBAAwB5B,OAAO6B,UAAU,GAAG,GAAS,CAAC,CAACC,SAAS,CAAC,CAACC,SAAS,GAAG,GAAG;CACvF,MAAMC,6BAA6B,IAAIC,KAAKA,KAAKC,IAAI,IAAI,MAAU,GAAI;CAEvER,KAAKE,wBAAwBA;CAC7BF,KAAKM,6BAA6BA;CAClC,MAAMN,KAAKS,KAAK;CAEhB,IAAI;EACF,MAAM9B,UAAU;GACd+B,IAAId;GACJe,SAAS,2BAA2BT;GACpCU,MAAM;+CACmCV,sBAAqB;;;GAG9DW,MAAM,6BAA6BX,sBAAqB;EAC1D,CAAC;CACH,SAASY,KAAK;EACZC,QAAQpB,MAAM,8BAA8BmB,GAAG;EAE/C,IAAI;GACF,MAAMhB,KAAKkB,UACT;IAAEC,KAAKjB,KAAKiB;IAAKf;GAAsB,GACvC,EACEgB,QAAQ;IACNhB,uBAAuB;IACvBI,4BAA4B;GAC9B,EACF,CACF;EACF,SAASa,YAAY;GACnBJ,QAAQpB,MAAM,8BAA8BwB,UAAU;EACxD;EAEAhC,IAAIM,IAAIC,OAAO,GAAG;EAClB,OAAO;GAAEF,SAAS;GAAOG,OAAO;EAAwB;CAC1D;CAEA,OAAO,EAAEH,SAAS,KAAK;AACzB;AAEA,IAAA,mBAAgB4B,QAA8B;CAC5CA,IAAIC,KAAKvC,OAAiBC,SAAS;AACrC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-DlaZThWe.js","names":["z","ROUTE","requestSchema","object","email","string","RequestPayload","infer","responseSchema","success","boolean","error","optional","retryAfterSeconds","number","int","positive","ResponsePayload","crypto","Api","ApiHandler","Ctx","models","sendEmail","getEmailAddressValidationErrorCode","AuthSessionUser","RequestPasswordReset","PASSWORD_RESET_COOLDOWN_SECONDS","PASSWORD_RESET_CODE_TTL_SECONDS","maskEmailForLog","email","localRaw","domainRaw","split","local","domain","localPrefix","slice","Math","min","length","localMask","repeat","max","domainTailLength","domainTail","domainMask","requestPasswordReset","RequestPayload","ResponsePayload","payload","ctx","Promise","parsed","requestSchema","safeParse","success","res","status","error","data","setHeader","String","retryAfterSeconds","User","getGlobal","requestedAt","Date","cooldownEligibleExpiresAt","getTime","passwordResetCode","randomInt","toString","padStart","passwordResetCodeExpiresAt","user","findOneAndUpdate","$or","$exists","$lte","$set","$unset","passwordResetToken","passwordResetTokenExpiresAt","returnDocument","existingUser","findOne","_id","console","info","to","subject","html","text","err","releaseResult","updateOne","modifiedCount","removeHeader","releaseErr","api","post","ROUTE"],"sources":["../src/api/request-password-reset/index.ts","../src/api/request-password-reset/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const ROUTE = \"/api/rb/auth/request-password-reset\"\n\nexport const requestSchema = z.object({\n email: z.string().email(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n success: z.boolean(),\n error: z.string().optional(),\n retryAfterSeconds: z.number().int().positive().optional(),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n","import crypto from \"crypto\"\n\nimport { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { sendEmail } from \"@rpcbase/server\"\n\nimport { getEmailAddressValidationErrorCode } from \"../../email-validation\"\nimport type { AuthSessionUser } from \"../../types\"\n\nimport * as RequestPasswordReset from \"./index\"\n\n\nexport const PASSWORD_RESET_COOLDOWN_SECONDS = 60\nconst PASSWORD_RESET_CODE_TTL_SECONDS = 10 * 60\n\nconst maskEmailForLog = (email: string) => {\n const [localRaw, domainRaw = \"\"] = email.split(\"@\")\n const local = localRaw || \"\"\n const domain = domainRaw || \"\"\n const localPrefix = local.slice(0, Math.min(3, local.length))\n const localMask = \"*\".repeat(Math.max(5, local.length - localPrefix.length))\n\n if (!domain) {\n return `${localPrefix}${localMask}@****`\n }\n\n const domainTailLength = Math.min(7, domain.length)\n const domainTail = domain.slice(-domainTailLength)\n const domainMask = \"*\".repeat(Math.max(4, domain.length - domainTailLength))\n\n return `${localPrefix}${localMask}@${domainMask}${domainTail}`\n}\n\n\nexport const requestPasswordReset: ApiHandler<\n RequestPasswordReset.RequestPayload,\n RequestPasswordReset.ResponsePayload,\n AuthSessionUser\n> = async(\n payload,\n ctx: Ctx<AuthSessionUser>\n): Promise<RequestPasswordReset.ResponsePayload> => {\n const parsed = RequestPasswordReset.requestSchema.safeParse(payload)\n\n if (!parsed.success) {\n ctx.res.status(400)\n return { success: false, error: \"invalid_payload\" }\n }\n\n const { email } = parsed.data\n\n if (await getEmailAddressValidationErrorCode(email)) {\n ctx.res.setHeader(\"Retry-After\", String(PASSWORD_RESET_COOLDOWN_SECONDS))\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n }\n\n const User = await models.getGlobal(\"RBUser\", ctx)\n const requestedAt = new Date()\n const cooldownEligibleExpiresAt = new Date(\n requestedAt.getTime() + (PASSWORD_RESET_CODE_TTL_SECONDS - PASSWORD_RESET_COOLDOWN_SECONDS) * 1000,\n )\n const passwordResetCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, \"0\")\n const passwordResetCodeExpiresAt = new Date(requestedAt.getTime() + PASSWORD_RESET_CODE_TTL_SECONDS * 1000)\n\n const user = await User.findOneAndUpdate(\n {\n email,\n $or: [\n { passwordResetCodeExpiresAt: { $exists: false } },\n { passwordResetCodeExpiresAt: { $lte: cooldownEligibleExpiresAt } },\n ],\n },\n {\n $set: {\n passwordResetCode,\n passwordResetCodeExpiresAt,\n },\n $unset: {\n passwordResetToken: \"\",\n passwordResetTokenExpiresAt: \"\",\n },\n },\n { returnDocument: \"after\" },\n )\n\n ctx.res.setHeader(\"Retry-After\", String(PASSWORD_RESET_COOLDOWN_SECONDS))\n\n if (!user) {\n const existingUser = await User.findOne({ email }, { _id: 1 })\n\n if (!existingUser) {\n try {\n console.info(`sending password reset account notice to email ${maskEmailForLog(email)}`)\n await sendEmail({\n to: email,\n subject: \"No account found for your password reset request\",\n html: `\n <p>Hello,</p>\n <p>You requested a new password reset code for this email address, but unfortunately we couldn't find an account associated with it.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n <p>If you're having trouble accessing your account, please contact support.</p>\n `,\n text: \"Hello, You requested a new password reset code for this email address, but unfortunately we couldn't find an account associated with it. If you didn't request this, you can ignore this message. If you're having trouble accessing your account, please contact support.\",\n })\n } catch (err) {\n console.error(\"failed to send password reset account notice email\", err)\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n }\n\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n }\n\n try {\n console.info(`sending password reset to email ${maskEmailForLog(email)}`)\n await sendEmail({\n to: email,\n subject: `Your password reset code: ${passwordResetCode}`,\n html: `\n <p>Your password reset code is <strong>${passwordResetCode}</strong>. It expires in 10 minutes.</p>\n <p>If you didn't request this, you can ignore this message.</p>\n `,\n text: `Your password reset code is ${passwordResetCode}. It expires in 10 minutes. If you didn't request this, you can ignore this message.`,\n })\n } catch (err) {\n console.error(\"failed to send password reset email\", err)\n\n try {\n const releaseResult = await User.updateOne(\n { _id: user._id, passwordResetCode },\n {\n $unset: {\n passwordResetCode: \"\",\n passwordResetCodeExpiresAt: \"\",\n },\n },\n )\n if (releaseResult.modifiedCount > 0) {\n ctx.res.removeHeader(\"Retry-After\")\n }\n } catch (releaseErr) {\n console.error(\"failed to release password reset cooldown\", releaseErr)\n }\n\n ctx.res.status(500)\n return { success: false, error: \"email_delivery_failed\" }\n }\n\n return { success: true, retryAfterSeconds: PASSWORD_RESET_COOLDOWN_SECONDS }\n}\n\nexport default (api: Api<AuthSessionUser>) => {\n api.post(RequestPasswordReset.ROUTE, requestPasswordReset)\n}\n"],"mappings":"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"}