@salesforce/webapp-template-feature-react-authentication-experimental 1.13.0 → 1.15.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.
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.15.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.14.0...v1.15.0) (2026-02-07)
7
+
8
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
9
+
10
+
11
+
12
+
13
+
14
+ # [1.14.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.13.0...v1.14.0) (2026-02-06)
15
+
16
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
17
+
18
+
19
+
20
+
21
+
6
22
  # [1.13.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.12.0...v1.13.0) (2026-02-06)
7
23
 
8
24
 
@@ -53,4 +53,16 @@ public without sharing class WebAppAuthUtils {
53
53
 
54
54
  return defaultUrl + url;
55
55
  }
56
+
57
+ /**
58
+ * Logs an error message and stack trace to the system debug log.
59
+ * It is only captured if a Trace Flag is active for the user.
60
+ *
61
+ * @param ex The exception to log.
62
+ * @param level The logging level to use.
63
+ */
64
+ public static void debugLog(Exception ex, LoggingLevel level) {
65
+ System.debug(level, 'Message: ' + ex.getMessage());
66
+ System.debug(level, 'Stack Trace: ' + ex.getStackTraceString());
67
+ }
56
68
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Web Applications REST endpoint for authenticated user password changes.
3
+ *
4
+ * Endpoint: POST /services/apexrest/auth/change-password
5
+ *
6
+ * Allows authenticated users to change their password by providing their current password
7
+ * and a new password. The new password is validated against org password policies.
8
+ *
9
+ * Security: Uses 'with sharing' to enforce user-level security. Only authenticated users
10
+ * can change their own password.
11
+ */
12
+ @RestResource(urlMapping='/auth/change-password')
13
+ global with sharing class WebAppChangePassword {
14
+
15
+ /**
16
+ * Changes the password for the currently authenticated user.
17
+ *
18
+ * @param newPassword The new password to set.
19
+ * @param currentPassword The user's current password for verification.
20
+ * @return SuccessPasswordChangeResponse on success, ErrorPasswordChangeResponse on failure.
21
+ */
22
+ @HttpPost
23
+ global static PasswordChangeResponse changePassword(String newPassword, String currentPassword) {
24
+ Savepoint sp = Database.setSavepoint();
25
+ try {
26
+ // newPassword and confirmPassword should be validated to be the same value on the client
27
+ Site.changePassword(newPassword, newPassword, currentPassword);
28
+ return new SuccessPasswordChangeResponse();
29
+ } catch (Exception ex) {
30
+ Database.rollback(sp);
31
+
32
+ // System.SecurityException is a user error but treat others as system errors
33
+ if (ex instanceof System.SecurityException) {
34
+ RestContext.response.statusCode = 400;
35
+ return new ErrorPasswordChangeResponse(ex.getMessage());
36
+ }
37
+
38
+ // Logs are only captured if a Trace Flag is active for the user
39
+ WebAppAuthUtils.debugLog(ex, LoggingLevel.ERROR);
40
+
41
+ RestContext.response.statusCode = 500;
42
+ return new ErrorPasswordChangeResponse('Password change failed');
43
+ }
44
+ }
45
+
46
+ /** Base response class for password change operations. */
47
+ global abstract class PasswordChangeResponse {
48
+
49
+ /** Success or failure of the password change operation */
50
+ private Boolean success;
51
+ }
52
+
53
+ /** Success response for password change. */
54
+ global class SuccessPasswordChangeResponse extends PasswordChangeResponse {
55
+
56
+ /** Constructs a success response. */
57
+ private SuccessPasswordChangeResponse() {
58
+ this.success = true;
59
+ }
60
+ }
61
+
62
+ /** Error response for password change. */
63
+ global class ErrorPasswordChangeResponse extends PasswordChangeResponse {
64
+
65
+ /** Error message describing the failure reason */
66
+ private String error;
67
+
68
+ /**
69
+ * Constructs an error response with the specified error message.
70
+ * @param error The error message to return to the client.
71
+ */
72
+ private ErrorPasswordChangeResponse(String error) {
73
+ this.success = false;
74
+ this.error = error;
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <apiVersion>66.0</apiVersion>
4
+ <status>Active</status>
5
+ </ApexClass>
@@ -18,6 +18,7 @@ interface AuthFormProps extends Omit<React.ComponentProps<"form">, "onSubmit"> {
18
18
  submit: {
19
19
  text: string;
20
20
  loadingText?: string;
21
+ disabled?: boolean;
21
22
  };
22
23
  footer?: {
23
24
  text?: string;
@@ -68,6 +69,7 @@ export function AuthForm({
68
69
  form={id}
69
70
  label={submit.text}
70
71
  loadingLabel={submit.loadingText}
72
+ disabled={submit.disabled}
71
73
  className="mt-6"
72
74
  />
73
75
  </form>
@@ -3,7 +3,7 @@ import { Spinner } from "../ui/spinner";
3
3
  import { cn } from "../../lib/utils";
4
4
  import { useFormContext } from "../../hooks/form";
5
5
 
6
- interface SubmitButtonProps extends Omit<React.ComponentProps<typeof Button>, "type" | "disabled"> {
6
+ interface SubmitButtonProps extends Omit<React.ComponentProps<typeof Button>, "type"> {
7
7
  /** Button text when not submitting */
8
8
  label: string;
9
9
  /** Button text while submitting */
@@ -18,15 +18,15 @@ const isSubmittingSelector = (state: { isSubmitting: boolean }) => state.isSubmi
18
18
  * Submit button that subscribes to form submission state.
19
19
  * UX Best Practice:
20
20
  * 1. Disables interaction immediately upon click (via isLoading) to prevent
21
- * accidental double-submissions which can cause race conditions in Auth APIs.
22
- * 2. Provides immediate visual feedback (Spinner) because Auth calls (e.g. hashing passwords,
23
- * generating sessions) can have variable latency.
21
+ * accidental double-submissions.
22
+ * 2. Provides immediate visual feedback (Spinner).
24
23
  */
25
24
  export function SubmitButton({
26
25
  label,
27
26
  loadingLabel = "Submitting…",
28
27
  className,
29
28
  form: formId,
29
+ disabled,
30
30
  ...props
31
31
  }: SubmitButtonProps) {
32
32
  const form = useFormContext();
@@ -37,7 +37,7 @@ export function SubmitButton({
37
37
  type="submit"
38
38
  form={formId}
39
39
  className={cn("w-full", className)}
40
- disabled={isSubmitting}
40
+ disabled={isSubmitting || disabled}
41
41
  {...props}
42
42
  >
43
43
  {isSubmitting && <Spinner className="mr-2" aria-hidden="true" />}
@@ -7,7 +7,6 @@ import { AuthForm } from "../components/forms/auth-form";
7
7
  import { useAppForm } from "../hooks/form";
8
8
  import { ROUTES, AUTH_PLACEHOLDERS } from "../utils/authenticationConfig";
9
9
  import { newPasswordSchema, handleApiResponse, getErrorMessage } from "../utils/helpers";
10
- import { getUser } from "../context/AuthContext";
11
10
 
12
11
  const changePasswordSchema = z
13
12
  .object({
@@ -18,25 +17,19 @@ const changePasswordSchema = z
18
17
  export default function ChangePassword() {
19
18
  const [success, setSuccess] = useState(false);
20
19
  const [submitError, setSubmitError] = useState<string | null>(null);
21
- const user = getUser();
22
20
 
23
21
  const form = useAppForm({
24
22
  defaultValues: { currentPassword: "", newPassword: "", confirmPassword: "" },
25
23
  validators: { onChange: changePasswordSchema, onSubmit: changePasswordSchema },
26
- onSubmit: async ({ value }) => {
24
+ onSubmit: async ({ value: formFieldValues }) => {
27
25
  setSubmitError(null);
28
26
  setSuccess(false);
29
27
  try {
30
28
  // [Dev Note] Custom Apex Endpoint: /auth/change-password
31
- // Security Critical:
32
- // We must pass the `user.id` (from AuthContext) to the Apex endpoint
33
- // to ensure the password change is applied to the correct user.
34
- // The Apex backend should also verify that the authenticated session matches this ID.
35
29
  // You must ensure this Apex class exists in your org
36
- const response = await baseClient.post("/services/apexrest/auth/change-password", {
37
- userId: user.id,
38
- currentPassword: value.currentPassword,
39
- newPassword: value.newPassword,
30
+ const response = await baseClient.post("/sfdcapi/services/apexrest/auth/change-password", {
31
+ currentPassword: formFieldValues.currentPassword,
32
+ newPassword: formFieldValues.newPassword,
40
33
  });
41
34
  await handleApiResponse(response, "Password change failed");
42
35
  setSuccess(true);
@@ -65,7 +58,7 @@ export default function ChangePassword() {
65
58
  </>
66
59
  )
67
60
  }
68
- submit={{ text: "Change Password", loadingText: "Changing…" }}
61
+ submit={{ text: "Change Password", loadingText: "Changing…", disabled: success }}
69
62
  footer={{ link: ROUTES.PROFILE.PATH, linkText: "Back to Profile" }}
70
63
  >
71
64
  <form.AppField name="currentPassword">
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-feature-react-authentication-experimental",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Authentication feature for web applications",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -19,7 +19,7 @@
19
19
  "watch": "npx tsx ../../cli/src/index.ts watch-patches packages/template/feature/feature-react-authentication packages/template/base-app/base-react-app packages/template/feature/feature-react-authentication/dist"
20
20
  },
21
21
  "devDependencies": {
22
- "@salesforce/webapp-experimental": "^1.13.0",
22
+ "@salesforce/webapp-experimental": "^1.15.0",
23
23
  "@tanstack/react-form": "^1.27.7",
24
24
  "@types/react": "^19.2.7",
25
25
  "@types/react-dom": "^19.2.3",
@@ -27,5 +27,5 @@
27
27
  "react-router": "^7.10.1",
28
28
  "vite": "^7.3.1"
29
29
  },
30
- "gitHead": "f5823a67b25f01d2c4510377cb4d2c34e7ace922"
30
+ "gitHead": "49bf85f6db4dd309d0fc36ad41157b45d9425b49"
31
31
  }