@evenicanpm/storefront-core 2.3.1 → 2.4.1

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.
Files changed (90) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +42 -9
  3. package/src/api-manager/datasources/d365/d365-address.datasource.ts +148 -61
  4. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +23 -1
  5. package/src/api-manager/datasources/d365/d365-order.datasource.ts +6 -1
  6. package/src/api-manager/datasources/d365/d365-user.datasource.ts +109 -37
  7. package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +44 -10
  8. package/src/api-manager/index.ts +2 -1
  9. package/src/api-manager/lib/get-graphql-client.ts +35 -7
  10. package/src/api-manager/schemas/product.schema.ts +1 -1
  11. package/src/api-manager/services/create-query.ts +36 -3
  12. package/src/api-manager/services/get-query-client.ts +10 -0
  13. package/src/auth/better-auth.ts +282 -15
  14. package/src/cms/blocks/block-manager.tsx +1 -1
  15. package/src/cms/blocks/components/product-section-fullwidth/index.tsx +3 -1
  16. package/src/cms/endpoints.ts +7 -2
  17. package/src/components/BazaarMenu.tsx +1 -1
  18. package/src/components/Typography.tsx +4 -2
  19. package/src/components/carousel-cards/carousel-card-1/CarouselCard1.stories.tsx +1 -1
  20. package/src/components/categories/category-list/category-list.tsx +9 -5
  21. package/src/components/categories/category-menu.tsx +1 -1
  22. package/src/components/countries-input.tsx +4 -0
  23. package/src/components/header/__tests__/user.test.tsx +5 -107
  24. package/src/components/header/components/user.tsx +72 -45
  25. package/src/components/header/sticky-header.tsx +0 -1
  26. package/src/components/navbar/mobile-menu/mobile-menu.test.tsx +1 -1
  27. package/src/components/navbar/utils/transform-nav.test.ts +1 -1
  28. package/src/components/navbar/utils/transform-nav.ts +1 -1
  29. package/src/components/product-cards/product-card/product-card.tsx +5 -2
  30. package/src/components/product-cards/product-card/styles/index.ts +1 -5
  31. package/src/components/products-view/compound/products-grid-view.tsx +5 -1
  32. package/src/components/products-view/compound/products-list-view.tsx +5 -1
  33. package/src/global.d.ts +3 -0
  34. package/src/hooks/use-nextauth-session.ts +0 -33
  35. package/src/lib/auth-client.ts +14 -0
  36. package/src/lib/auth.ts +4 -0
  37. package/src/lib/entra-native-auth.ts +138 -0
  38. package/src/lib/native-session.ts +434 -0
  39. package/src/pages/account/addresses/address-form.tsx +20 -2
  40. package/src/pages/account/profile/__tests__/profile-form.test.tsx +173 -0
  41. package/src/pages/account/profile/profile-form.tsx +285 -0
  42. package/src/pages/account/profile/profile-validation.ts +52 -0
  43. package/src/pages/account/wishlist/wishlist-item.tsx +1 -2
  44. package/src/pages/auth/auth-activate-page.tsx +509 -0
  45. package/src/pages/auth/auth-layout.tsx +73 -0
  46. package/src/pages/auth/auth-login-page.tsx +241 -0
  47. package/src/pages/auth/auth-reset-password-page.tsx +487 -0
  48. package/src/pages/auth/compound/auth-form.tsx +182 -0
  49. package/src/pages/auth/compound/auth-pages.tsx +21 -0
  50. package/src/pages/auth/lib/friendly-error.ts +70 -0
  51. package/src/pages/auth/lib/index.ts +2 -0
  52. package/src/pages/auth/lib/types.ts +5 -0
  53. package/src/pages/cart/cart-item.tsx +1 -7
  54. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +14 -3
  55. package/src/pages/checkout/checkout-alt-form/steps/address/address-card.tsx +5 -3
  56. package/src/pages/checkout/checkout-alt-form/steps/address/delivery-address.tsx +20 -6
  57. package/src/pages/checkout/checkout-alt-form/steps/address/new-address-form.tsx +1 -1
  58. package/src/pages/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +1 -1
  59. package/src/pages/confirmation/ordered-products.tsx +3 -1
  60. package/src/pages/product-details/bopis/find-in-store-modal.tsx +4 -4
  61. package/src/pages/product-details/bopis/pickup-option-select.tsx +2 -2
  62. package/src/pages/product-details/bopis/search-header.tsx +2 -2
  63. package/src/pages/product-details/product-intro/compound/context.ts +8 -3
  64. package/src/pages/product-details/product-intro/compound/product-info.tsx +25 -19
  65. package/src/pages/product-list/product-list-view.tsx +2 -1
  66. package/src/providers/nav-provider/index.tsx +1 -1
  67. package/src/providers/nav-provider/utils/createLink.ts +1 -1
  68. package/tsconfig.json +20 -15
  69. package/__mocks__/countries.ts +0 -11
  70. package/__mocks__/create-mutation.ts +0 -68
  71. package/__mocks__/create-query.ts +0 -94
  72. package/__mocks__/data/categories.json +0 -795
  73. package/__mocks__/get-product-by-id.ts +0 -6
  74. package/__mocks__/next-auth-react.ts +0 -9
  75. package/__mocks__/next-font.js +0 -4
  76. package/__mocks__/next-headers.js +0 -13
  77. package/__mocks__/next-image.tsx +0 -18
  78. package/__mocks__/next-link.js +0 -19
  79. package/__mocks__/next-navigation.ts +0 -29
  80. package/__mocks__/product-dimensions.ts +0 -635
  81. package/__mocks__/products.ts +0 -533
  82. package/__mocks__/query-client.ts +0 -3
  83. package/__mocks__/wishlists.json +0 -408
  84. package/src/auth/msal.ts +0 -68
  85. package/src/cms/blog.ts +0 -229
  86. package/src/cms/interfaces/navigation.ts +0 -52
  87. package/src/cms/interfaces/product-data.ts +0 -83
  88. package/src/cms/pages.ts +0 -149
  89. package/src/pages/account/profile/profile-button.test.tsx +0 -59
  90. package/src/pages/account/profile/profile-button.tsx +0 -51
@@ -2,13 +2,8 @@
2
2
 
3
3
  import useSessionInit from "@evenicanpm/storefront-core/src/api-manager/services/session/mutations/session-init";
4
4
  import useSessionLogout from "@evenicanpm/storefront-core/src/api-manager/services/session/mutations/session-logout";
5
- // API hooks
6
5
  import useUpdateUser from "@evenicanpm/storefront-core/src/api-manager/services/user/mutations/update-user";
7
6
  import getUser from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user";
8
- import {
9
- getMsalInstance,
10
- initializeMsal,
11
- } from "@evenicanpm/storefront-core/src/auth/msal";
12
7
  import signOutCustom from "@evenicanpm/storefront-core/src/auth/signout";
13
8
  import MobileUserIcon from "@evenicanpm/storefront-core/src/components/icons/user";
14
9
  import { NavLink } from "@evenicanpm/storefront-core/src/components/nav-link";
@@ -27,13 +22,16 @@ import {
27
22
  ListItemButton,
28
23
  Popover,
29
24
  } from "@mui/material";
30
- import { genericOAuthClient } from "better-auth/client/plugins";
25
+ import {
26
+ customSessionClient,
27
+ genericOAuthClient,
28
+ } from "better-auth/client/plugins";
31
29
  // Auth components
32
30
  import { createAuthClient } from "better-auth/react";
33
31
  import { useRouter } from "next/navigation";
34
32
 
35
33
  const authClient = createAuthClient({
36
- plugins: [genericOAuthClient()],
34
+ plugins: [genericOAuthClient(), customSessionClient()],
37
35
  });
38
36
  const { useSession: useBetterAuthSession } = authClient;
39
37
 
@@ -44,15 +42,11 @@ import {
44
42
  type ReactNode,
45
43
  useContext,
46
44
  useEffect,
45
+ useRef,
47
46
  useState,
48
47
  } from "react";
49
48
  import { MdPersonOutline as PersonOutline } from "react-icons/md";
50
49
 
51
- interface IdTokenClaims {
52
- given_name?: string;
53
- family_name?: string;
54
- }
55
-
56
50
  const routeToMenuItem = (route: AccountRoute) => ({
57
51
  href: route.href,
58
52
  label: `Account.Menu.${route.titleKey}`,
@@ -104,47 +98,80 @@ const User = ({ children }: Props) => {
104
98
  }
105
99
 
106
100
  const isAuthenticated = !!session;
101
+ const isE4Datasource = (process.env.API_MANAGER_PRIMARY ?? "e4") === "e4";
102
+ const isD365Datasource = !isE4Datasource;
107
103
  const { mutateAsync: sessionInit } = useSessionInit();
108
- const { mutateAsync: updateUser } = useUpdateUser();
109
104
  const { mutateAsync: sessionLogout } = useSessionLogout();
105
+ const { mutateAsync: updateUser } = useUpdateUser();
106
+ const syncedUserKeyRef = useRef<string>("");
110
107
  // Register the "me" query on the client so the hydrated cache entry has a queryFn for refetches
111
- getUser.useData();
108
+ const { data: currentUser, isPending: isUserPending } = getUser.useData();
112
109
 
113
110
  // session init
114
111
  useEffect(() => {
115
- if (isAuthenticated && session?.user?.email) {
116
- const accessToken =
117
- (session.session as { accessToken?: string })?.accessToken || "";
112
+ const accessToken =
113
+ (session?.session as { accessToken?: string } | undefined)?.accessToken ||
114
+ "";
115
+
116
+ if (
117
+ isE4Datasource &&
118
+ isAuthenticated &&
119
+ session?.user?.email &&
120
+ accessToken
121
+ ) {
118
122
  sessionInit({ accessToken, email: session.user.email }).catch(
119
123
  console.error,
120
124
  );
121
125
  }
122
- }, [isAuthenticated, session, sessionInit]);
126
+ }, [isE4Datasource, isAuthenticated, session, sessionInit]);
123
127
 
124
- // Handles MSAL redirect - which happens for edit profile and reset password use cases in account/profile page.
128
+ // For D365, only seed profile details from the auth session when D365 does not
129
+ // already have a name. This avoids overwriting explicit profile edits with a
130
+ // stale session.user.name snapshot.
125
131
  useEffect(() => {
126
- const handleRedirect = async () => {
127
- try {
128
- await initializeMsal();
129
- const res = await getMsalInstance().handleRedirectPromise();
130
- if (res) {
131
- const { given_name, family_name } =
132
- res.idTokenClaims as IdTokenClaims;
133
- // Update the API when profile info is updated through MSAL
134
- if (given_name && family_name) {
135
- updateUser({
136
- AccountNumber: "",
137
- FirstName: given_name,
138
- LastName: family_name,
139
- }).catch(console.error);
140
- }
141
- }
142
- } catch (err) {
143
- console.error("MSAL redirect error:", err);
144
- }
145
- };
146
- handleRedirect();
147
- }, [updateUser]);
132
+ if (!isD365Datasource || !isAuthenticated || isUserPending) return;
133
+
134
+ const existingFirstName =
135
+ typeof currentUser?.FirstName === "string"
136
+ ? currentUser.FirstName.trim()
137
+ : "";
138
+ const existingLastName =
139
+ typeof currentUser?.LastName === "string"
140
+ ? currentUser.LastName.trim()
141
+ : "";
142
+ if (existingFirstName || existingLastName) return;
143
+
144
+ const fullName =
145
+ typeof session?.user?.name === "string" ? session.user.name.trim() : "";
146
+ const [firstName = "", ...lastParts] = fullName
147
+ .split(/\s+/)
148
+ .filter(Boolean);
149
+ const lastName = lastParts.join(" ");
150
+
151
+ if (!firstName && !lastName) return;
152
+
153
+ const syncKey = `${session?.user?.email || ""}:${firstName}:${lastName}`;
154
+ if (syncedUserKeyRef.current === syncKey) return;
155
+
156
+ syncedUserKeyRef.current = syncKey;
157
+ updateUser({
158
+ AccountNumber: "",
159
+ Email: session?.user?.email || undefined,
160
+ FirstName: firstName || undefined,
161
+ LastName: lastName || undefined,
162
+ }).catch((error) => {
163
+ // Allow retry on next render when sync fails.
164
+ syncedUserKeyRef.current = "";
165
+ console.warn("D365 user sync error:", error);
166
+ });
167
+ }, [
168
+ currentUser,
169
+ isD365Datasource,
170
+ isAuthenticated,
171
+ isUserPending,
172
+ session,
173
+ updateUser,
174
+ ]);
148
175
 
149
176
  const handleLogout = async () => {
150
177
  try {
@@ -156,10 +183,10 @@ const User = ({ children }: Props) => {
156
183
  };
157
184
 
158
185
  const handleSignIn = () => {
159
- authClient.signIn.oauth2({
160
- providerId: "entra-external-id",
161
- callbackURL: "/",
162
- });
186
+ const callbackUrl = encodeURIComponent(
187
+ globalThis.location.pathname + globalThis.location.search,
188
+ );
189
+ globalThis.location.href = `/login?callbackUrl=${callbackUrl}`;
163
190
  };
164
191
 
165
192
  const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
@@ -8,7 +8,6 @@ import { Sticky } from "@evenicanpm/ui";
8
8
  import useTheme from "@mui/material/styles/useTheme";
9
9
  import useMediaQuery from "@mui/material/useMediaQuery";
10
10
  import { useCallback, useState } from "react";
11
- import { AccountRouteOverrides } from "../../pages/account/account-routes";
12
11
 
13
12
  /**
14
13
  * @category Implementation
@@ -1,9 +1,9 @@
1
+ import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
1
2
  import MobileMenu from "@evenicanpm/storefront-core/src/components/navbar/mobile-menu/mobile-menu";
2
3
  import { transformCmsNav } from "@evenicanpm/storefront-core/src/components/navbar/utils/transform-nav";
3
4
  import { fireEvent, render, screen } from "@testing-library/react";
4
5
  import type React from "react";
5
6
  import { beforeEach, describe, it, vi } from "vitest";
6
- import type { CmsNavItem } from "@/cms/interfaces/navigation";
7
7
 
8
8
  const useContext = vi.fn();
9
9
 
@@ -1,10 +1,10 @@
1
+ import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
1
2
  import {
2
3
  transformCategoryNav,
3
4
  transformCmsNav,
4
5
  } from "@evenicanpm/storefront-core/src/components/navbar/utils/transform-nav";
5
6
  import type { CategoryHierarchy } from "@evenicanpm/storefront-core/src/lib/category-helpers";
6
7
  import { describe, expect, it } from "vitest";
7
- import type { CmsNavItem } from "@/cms/interfaces/navigation";
8
8
 
9
9
  describe("transform nav items", () => {
10
10
  it("should recursively transform deeply nested categories and its children with correct href, title, and items", () => {
@@ -1,6 +1,6 @@
1
+ import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
1
2
  import type { CategoryHierarchy } from "@evenicanpm/storefront-core/src/lib/category-helpers";
2
3
  import type { SvgIconComponent } from "@mui/icons-material";
3
- import type { CmsNavItem } from "@/cms/interfaces/navigation";
4
4
 
5
5
  type MenuType = "default" | "mega-menu" | "full-screen-mega-menu";
6
6
  export interface TransformedNavItem {
@@ -60,8 +60,11 @@ function findChild(
60
60
  if (!React.isValidElement(child)) return;
61
61
  if (child.type === component) {
62
62
  found = true;
63
- } else if (child.props?.children) {
64
- found ||= findChild(child.props.children, component);
63
+ } else if ((child.props as { children?: ReactNode })?.children) {
64
+ found ||= findChild(
65
+ (child.props as { children?: ReactNode }).children,
66
+ component,
67
+ );
65
68
  }
66
69
  });
67
70
 
@@ -1,7 +1,6 @@
1
1
  // GLOBAL CUSTOM COMPONENT
2
2
  import BazaarCard from "@evenicanpm/storefront-core/src/components/BazaarCard";
3
3
  import styled from "@mui/material/styles/styled";
4
- import type { StyledComponent } from "@mui/styled-engine";
5
4
 
6
5
  export const StyledBazaarCard = styled(BazaarCard)({
7
6
  height: "100%",
@@ -46,7 +45,4 @@ export const ContentWrapper = styled("div")({
46
45
  },
47
46
  });
48
47
 
49
- const StyledBazaarCardType: StyledComponent<typeof BazaarCard, object, "div"> =
50
- StyledBazaarCard as StyledComponent<typeof BazaarCard, object, "div">;
51
-
52
- export default StyledBazaarCardType;
48
+ export default StyledBazaarCard;
@@ -52,7 +52,11 @@ export default function ProductsGridView(props: Readonly<ProductsViewProps>) {
52
52
  * @category Sub-Component
53
53
  *
54
54
  */
55
- ProductsGridView.Grid = ({ children }: { children: React.ReactElement }) => {
55
+ ProductsGridView.Grid = ({
56
+ children,
57
+ }: {
58
+ children: React.ReactElement<Record<string, unknown>>;
59
+ }) => {
56
60
  const { products, productPrices } = useProductsView();
57
61
 
58
62
  return (
@@ -38,7 +38,11 @@ export default function ProductsListView(props: Readonly<ProductsViewProps>) {
38
38
  * @category Sub-Component
39
39
  *
40
40
  */
41
- ProductsListView.List = ({ children }: { children: React.ReactElement }) => {
41
+ ProductsListView.List = ({
42
+ children,
43
+ }: {
44
+ children: React.ReactElement<Record<string, unknown>>;
45
+ }) => {
42
46
  const { products, productPrices } = useProductsView();
43
47
  return (
44
48
  <>
@@ -0,0 +1,3 @@
1
+ declare module "*.css";
2
+ declare module "*.scss";
3
+ declare module "*.sass";
@@ -1,12 +1,6 @@
1
1
  // hooks/useAppSession.ts
2
2
 
3
3
  import useSessionInit from "@evenicanpm/storefront-core/src/api-manager/services/session/mutations/session-init";
4
- // API hooks
5
- import useUpdateUser from "@evenicanpm/storefront-core/src/api-manager/services/user/mutations/update-user";
6
- import {
7
- getMsalInstance,
8
- initializeMsal,
9
- } from "@evenicanpm/storefront-core/src/auth/msal";
10
4
  import { createAuthClient } from "better-auth/react";
11
5
  import { useEffect } from "react";
12
6
 
@@ -16,7 +10,6 @@ const { useSession: useBetterAuthSession } = authClient;
16
10
  export function useAppSession() {
17
11
  const { data: session, isPending } = useBetterAuthSession();
18
12
  const { mutateAsync: sessionInit } = useSessionInit();
19
- const { mutateAsync: updateUser } = useUpdateUser();
20
13
 
21
14
  // Initialize session in API
22
15
  useEffect(() => {
@@ -30,32 +23,6 @@ export function useAppSession() {
30
23
  }
31
24
  }, [session, sessionInit]);
32
25
 
33
- // MSAL redirect handling
34
- useEffect(() => {
35
- const handleRedirect = async () => {
36
- try {
37
- await initializeMsal();
38
- const res = await getMsalInstance().handleRedirectPromise();
39
- if (res) {
40
- const { given_name, family_name } = res.idTokenClaims as {
41
- given_name?: string;
42
- family_name?: string;
43
- };
44
- if (given_name && family_name) {
45
- updateUser({
46
- AccountNumber: "",
47
- FirstName: given_name,
48
- LastName: family_name,
49
- }).catch(console.error);
50
- }
51
- }
52
- } catch (err) {
53
- console.error("MSAL redirect error:", err);
54
- }
55
- };
56
- handleRedirect();
57
- }, [updateUser]);
58
-
59
26
  return {
60
27
  session,
61
28
  isAuthenticated: !!session,
@@ -0,0 +1,14 @@
1
+ import type { auth } from "@evenicanpm/storefront-core/src/lib/auth";
2
+ import {
3
+ customSessionClient,
4
+ genericOAuthClient,
5
+ } from "better-auth/client/plugins";
6
+ import { createAuthClient } from "better-auth/react";
7
+
8
+ const authClient = createAuthClient({
9
+ plugins: [genericOAuthClient(), customSessionClient<typeof auth>()],
10
+ });
11
+
12
+ export const { signIn, signOut, signUp, useSession, getSession } = authClient;
13
+
14
+ export type Session = typeof auth.$Infer.Session;
@@ -0,0 +1,4 @@
1
+ export {
2
+ auth,
3
+ type Session,
4
+ } from "@evenicanpm/storefront-core/src/auth/better-auth";
@@ -0,0 +1,138 @@
1
+ const CLIENT_ID = process.env.NEXT_PUBLIC_ENTRA_EXTERNAL_ID_CLIENT_ID || "";
2
+ const TENANT = process.env.NEXT_PUBLIC_ENTRA_EXTERNAL_ID_TENANT_NAME || "";
3
+ const CUSTOM_SCOPES =
4
+ process.env.NEXT_PUBLIC_ENTRA_EXTERNAL_ID_CUSTOM_SCOPES ||
5
+ "openid offline_access email profile";
6
+
7
+ function baseUrl() {
8
+ return `https://${TENANT}.ciamlogin.com/${TENANT}.onmicrosoft.com`;
9
+ }
10
+
11
+ async function post(path: string, params: Record<string, string>) {
12
+ const res = await fetch(`${baseUrl()}${path}`, {
13
+ method: "POST",
14
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
15
+ body: new URLSearchParams(params).toString(),
16
+ cache: "no-store",
17
+ });
18
+ return res.json() as Promise<Record<string, unknown>>;
19
+ }
20
+
21
+ export function nativeLoginInitiate(username: string) {
22
+ return post("/oauth2/v2.0/initiate", {
23
+ client_id: CLIENT_ID,
24
+ challenge_type: "password redirect",
25
+ username,
26
+ capabilities: "mfa_required registration_required",
27
+ });
28
+ }
29
+
30
+ export function nativeLoginChallenge(continuationToken: string) {
31
+ return post("/oauth2/v2.0/challenge", {
32
+ client_id: CLIENT_ID,
33
+ challenge_type: "password redirect",
34
+ continuation_token: continuationToken,
35
+ capabilities: "mfa_required registration_required",
36
+ });
37
+ }
38
+
39
+ export function nativeLoginToken(continuationToken: string, password: string) {
40
+ return post("/oauth2/v2.0/token", {
41
+ client_id: CLIENT_ID,
42
+ grant_type: "password",
43
+ password,
44
+ scope: CUSTOM_SCOPES,
45
+ continuation_token: continuationToken,
46
+ });
47
+ }
48
+
49
+ export function signupStart(
50
+ username: string,
51
+ password: string,
52
+ givenName?: string,
53
+ surname?: string,
54
+ displayName?: string,
55
+ ) {
56
+ const params: Record<string, string> = {
57
+ client_id: CLIENT_ID,
58
+ username,
59
+ password,
60
+ challenge_type: "oob password redirect",
61
+ };
62
+
63
+ if (givenName || surname || displayName) {
64
+ params.attributes = JSON.stringify({
65
+ ...(givenName ? { givenName } : {}),
66
+ ...(surname ? { surname } : {}),
67
+ ...(displayName ? { displayName } : {}),
68
+ });
69
+ }
70
+
71
+ return post("/signup/v1.0/start", params);
72
+ }
73
+
74
+ export function signupChallenge(continuationToken: string) {
75
+ return post("/signup/v1.0/challenge", {
76
+ client_id: CLIENT_ID,
77
+ challenge_type: "oob password redirect",
78
+ continuation_token: continuationToken,
79
+ });
80
+ }
81
+
82
+ export function signupContinueOtp(continuationToken: string, oob: string) {
83
+ return post("/signup/v1.0/continue", {
84
+ client_id: CLIENT_ID,
85
+ continuation_token: continuationToken,
86
+ grant_type: "oob",
87
+ oob,
88
+ });
89
+ }
90
+
91
+ export function getTokenAfterSignup(continuationToken: string) {
92
+ return post("/oauth2/v2.0/token", {
93
+ client_id: CLIENT_ID,
94
+ continuation_token: continuationToken,
95
+ grant_type: "continuation_token",
96
+ scope: CUSTOM_SCOPES,
97
+ });
98
+ }
99
+
100
+ export function ssrpStart(username: string) {
101
+ return post("/resetpassword/v1.0/start", {
102
+ client_id: CLIENT_ID,
103
+ username,
104
+ challenge_type: "oob redirect",
105
+ });
106
+ }
107
+
108
+ export function ssrpChallenge(continuationToken: string) {
109
+ return post("/resetpassword/v1.0/challenge", {
110
+ client_id: CLIENT_ID,
111
+ challenge_type: "oob redirect",
112
+ continuation_token: continuationToken,
113
+ });
114
+ }
115
+
116
+ export function ssrpContinueOtp(continuationToken: string, oob: string) {
117
+ return post("/resetpassword/v1.0/continue", {
118
+ client_id: CLIENT_ID,
119
+ continuation_token: continuationToken,
120
+ grant_type: "oob",
121
+ oob,
122
+ });
123
+ }
124
+
125
+ export function ssrpSubmit(continuationToken: string, newPassword: string) {
126
+ return post("/resetpassword/v1.0/submit", {
127
+ client_id: CLIENT_ID,
128
+ continuation_token: continuationToken,
129
+ new_password: newPassword,
130
+ });
131
+ }
132
+
133
+ export function ssrpPollCompletion(continuationToken: string) {
134
+ return post("/resetpassword/v1.0/poll_completion", {
135
+ client_id: CLIENT_ID,
136
+ continuation_token: continuationToken,
137
+ });
138
+ }