@carlonicora/nextjs-jsonapi 1.40.1 → 1.41.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.
Files changed (165) hide show
  1. package/README.md +3 -3
  2. package/dist/AuthComponent-BuWc2C4g.d.ts +28 -0
  3. package/dist/AuthComponent-fLVGdvSr.d.mts +28 -0
  4. package/dist/{BlockNoteEditor-EKY4AHVK.mjs → BlockNoteEditor-B3RQ4VQ7.mjs} +5 -5
  5. package/dist/{BlockNoteEditor-4G3L3LSF.js → BlockNoteEditor-VUAWVZF4.js} +15 -15
  6. package/dist/{BlockNoteEditor-4G3L3LSF.js.map → BlockNoteEditor-VUAWVZF4.js.map} +1 -1
  7. package/dist/JsonApiRequest-MUPAO7DI.js +24 -0
  8. package/dist/{JsonApiRequest-GR3L56A5.js.map → JsonApiRequest-MUPAO7DI.js.map} +1 -1
  9. package/dist/{JsonApiRequest-K5BRU7RE.mjs → JsonApiRequest-XCQHVVYD.mjs} +2 -2
  10. package/dist/auth.interface-8XglqHir.d.mts +33 -0
  11. package/dist/auth.interface-BJGKQ0zr.d.ts +33 -0
  12. package/dist/billing/index.js +409 -415
  13. package/dist/billing/index.js.map +1 -1
  14. package/dist/billing/index.mjs +4 -10
  15. package/dist/billing/index.mjs.map +1 -1
  16. package/dist/{chunk-BAOP6PTD.mjs → chunk-BJNQZGMN.mjs} +1618 -666
  17. package/dist/chunk-BJNQZGMN.mjs.map +1 -0
  18. package/dist/{chunk-U4MTVHOC.mjs → chunk-GCQUTWZ2.mjs} +11 -4
  19. package/dist/{chunk-U4MTVHOC.mjs.map → chunk-GCQUTWZ2.mjs.map} +1 -1
  20. package/dist/{chunk-ZNGEVB5M.js → chunk-L5F5ZN5F.js} +960 -140
  21. package/dist/chunk-L5F5ZN5F.js.map +1 -0
  22. package/dist/{chunk-RRIYLEY6.mjs → chunk-LBIC4GJK.mjs} +2 -2
  23. package/dist/{chunk-T5YYOT4Z.js → chunk-OODZEX6P.js} +3 -3
  24. package/dist/{chunk-T5YYOT4Z.js.map → chunk-OODZEX6P.js.map} +1 -1
  25. package/dist/{chunk-GVN7XC3U.mjs → chunk-PHNL4QUF.mjs} +835 -15
  26. package/dist/chunk-PHNL4QUF.mjs.map +1 -0
  27. package/dist/{chunk-GKY5DAIH.js → chunk-QPWHMXE2.js} +1505 -553
  28. package/dist/chunk-QPWHMXE2.js.map +1 -0
  29. package/dist/{chunk-FM6WRAN5.js → chunk-WLS4D6VG.js} +12 -5
  30. package/dist/chunk-WLS4D6VG.js.map +1 -0
  31. package/dist/client/index.d.mts +4 -4
  32. package/dist/client/index.d.ts +4 -4
  33. package/dist/client/index.js +5 -5
  34. package/dist/client/index.mjs +4 -4
  35. package/dist/components/index.d.mts +69 -8
  36. package/dist/components/index.d.ts +69 -8
  37. package/dist/components/index.js +27 -5
  38. package/dist/components/index.js.map +1 -1
  39. package/dist/components/index.mjs +26 -4
  40. package/dist/{config-BxwhHdCD.d.mts → config-BW5u1e9P.d.mts} +1 -1
  41. package/dist/{config-BbaBV_yk.d.ts → config-BozK5PY0.d.ts} +1 -1
  42. package/dist/{content.interface-CgUu4771.d.ts → content.interface-CpCDB1Uk.d.ts} +1 -1
  43. package/dist/{content.interface-CWV0q4lZ.d.mts → content.interface-b-mzkL_q.d.mts} +1 -1
  44. package/dist/contexts/index.d.mts +2 -2
  45. package/dist/contexts/index.d.ts +2 -2
  46. package/dist/contexts/index.js +5 -5
  47. package/dist/contexts/index.mjs +4 -4
  48. package/dist/core/index.d.mts +407 -7
  49. package/dist/core/index.d.ts +407 -7
  50. package/dist/core/index.js +61 -3
  51. package/dist/core/index.js.map +1 -1
  52. package/dist/core/index.mjs +60 -2
  53. package/dist/index.d.mts +8 -6
  54. package/dist/index.d.ts +8 -6
  55. package/dist/index.js +62 -4
  56. package/dist/index.js.map +1 -1
  57. package/dist/index.mjs +61 -3
  58. package/dist/{notification.interface-XARGKJAq.d.ts → notification.interface-CR2PuV6Y.d.ts} +1 -0
  59. package/dist/{notification.interface-DIln2r7X.d.mts → notification.interface-D241WNUx.d.mts} +1 -0
  60. package/dist/{s3.service-BoOF5-ln.d.mts → s3.service-D0rbmLFp.d.mts} +10 -31
  61. package/dist/{s3.service-Mxo-7wQ6.d.ts → s3.service-DOwqcUDT.d.ts} +10 -31
  62. package/dist/scripts/generate-web-module/generator.js +26 -26
  63. package/dist/scripts/generate-web-module/generator.js.map +1 -1
  64. package/dist/scripts/generate-web-module/utils/file-writer.js +9 -9
  65. package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -1
  66. package/dist/server/index.d.mts +4 -3
  67. package/dist/server/index.d.ts +4 -3
  68. package/dist/server/index.js +12 -12
  69. package/dist/server/index.mjs +2 -2
  70. package/dist/{useSocket-awibcC9B.d.ts → useSocket-CC8SkXdm.d.ts} +1 -1
  71. package/dist/{useSocket-BILAdmZ0.d.mts → useSocket-CttIHn2P.d.mts} +1 -1
  72. package/package.json +4 -1
  73. package/scripts/generate-web-module/generator.ts +26 -26
  74. package/scripts/generate-web-module/utils/file-writer.ts +9 -9
  75. package/src/components/pages/PageContentContainer.tsx +22 -9
  76. package/src/core/abstracts/AbstractService.ts +2 -0
  77. package/src/core/factories/JsonApiDataFactory.ts +2 -1
  78. package/src/core/index.ts +14 -0
  79. package/src/core/registry/DataClassRegistry.ts +7 -1
  80. package/src/core/registry/ModuleRegistry.ts +15 -0
  81. package/src/features/auth/backup-code-verify.module.ts +9 -0
  82. package/src/features/auth/components/containers/SecurityContainer.tsx +11 -0
  83. package/src/features/auth/components/containers/index.ts +1 -0
  84. package/src/features/auth/components/forms/Login.tsx +15 -3
  85. package/src/features/auth/components/forms/Register.tsx +1 -9
  86. package/src/features/auth/components/forms/TwoFactorChallenge.tsx +202 -0
  87. package/src/features/auth/components/forms/index.ts +1 -0
  88. package/src/features/auth/components/index.ts +1 -0
  89. package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +148 -0
  90. package/src/features/auth/components/two-factor/DisableTwoFactorDialog.tsx +74 -0
  91. package/src/features/auth/components/two-factor/PasskeyButton.tsx +59 -0
  92. package/src/features/auth/components/two-factor/PasskeyList.tsx +172 -0
  93. package/src/features/auth/components/two-factor/PasskeySetupDialog.tsx +105 -0
  94. package/src/features/auth/components/two-factor/TotpAuthenticatorList.tsx +104 -0
  95. package/src/features/auth/components/two-factor/TotpInput.tsx +90 -0
  96. package/src/features/auth/components/two-factor/TotpSetupDialog.tsx +161 -0
  97. package/src/features/auth/components/two-factor/TwoFactorSettings.tsx +175 -0
  98. package/src/features/auth/components/two-factor/index.ts +9 -0
  99. package/src/features/auth/contexts/AuthContext.tsx +9 -0
  100. package/src/features/auth/data/auth.service.ts +18 -1
  101. package/src/features/auth/data/backup-code-verify.ts +20 -0
  102. package/src/features/auth/data/index.ts +21 -0
  103. package/src/features/auth/data/passkey-authentication-options.interface.ts +7 -0
  104. package/src/features/auth/data/passkey-authentication-options.ts +37 -0
  105. package/src/features/auth/data/passkey-registration-options.ts +46 -0
  106. package/src/features/auth/data/passkey-registration-verify.ts +62 -0
  107. package/src/features/auth/data/passkey-rename.ts +20 -0
  108. package/src/features/auth/data/passkey-verify-login.ts +23 -0
  109. package/src/features/auth/data/passkey.interface.ts +9 -0
  110. package/src/features/auth/data/passkey.ts +40 -0
  111. package/src/features/auth/data/totp-authenticator.interface.ts +7 -0
  112. package/src/features/auth/data/totp-authenticator.ts +28 -0
  113. package/src/features/auth/data/totp-setup.interface.ts +5 -0
  114. package/src/features/auth/data/totp-setup.ts +48 -0
  115. package/src/features/auth/data/totp-verify-login.ts +20 -0
  116. package/src/features/auth/data/totp-verify.ts +22 -0
  117. package/src/features/auth/data/two-factor-challenge.interface.ts +7 -0
  118. package/src/features/auth/data/two-factor-challenge.ts +45 -0
  119. package/src/features/auth/data/two-factor-enable.ts +20 -0
  120. package/src/features/auth/data/two-factor-status.interface.ts +11 -0
  121. package/src/features/auth/data/two-factor-status.ts +40 -0
  122. package/src/features/auth/data/two-factor.service.ts +331 -0
  123. package/src/features/auth/enums/AuthComponent.ts +1 -0
  124. package/src/features/auth/index.ts +13 -0
  125. package/src/features/auth/passkey-authentication-options.module.ts +9 -0
  126. package/src/features/auth/passkey-registration-options.module.ts +9 -0
  127. package/src/features/auth/passkey-registration-verify.module.ts +9 -0
  128. package/src/features/auth/passkey-rename.module.ts +9 -0
  129. package/src/features/auth/passkey-verify-login.module.ts +9 -0
  130. package/src/features/auth/passkey.module.ts +9 -0
  131. package/src/features/auth/totp-authenticator.module.ts +9 -0
  132. package/src/features/auth/totp-setup.module.ts +9 -0
  133. package/src/features/auth/totp-verify-login.module.ts +9 -0
  134. package/src/features/auth/totp-verify.module.ts +9 -0
  135. package/src/features/auth/two-factor-challenge.module.ts +9 -0
  136. package/src/features/auth/two-factor-enable.module.ts +9 -0
  137. package/src/features/auth/two-factor-status.module.ts +9 -0
  138. package/src/features/billing/modules/billing.module.ts +1 -0
  139. package/src/features/billing/stripe-customer/stripe-customer.module.ts +1 -0
  140. package/src/features/billing/stripe-customer/stripe-payment-method.module.ts +1 -0
  141. package/src/features/billing/stripe-invoice/stripe-invoice.module.ts +1 -0
  142. package/src/features/billing/stripe-price/stripe-price.module.ts +1 -0
  143. package/src/features/billing/stripe-product/stripe-product.module.ts +1 -0
  144. package/src/features/billing/stripe-promotion-code/stripe-promotion-code.module.ts +1 -0
  145. package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +0 -5
  146. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +0 -8
  147. package/src/features/billing/stripe-subscription/stripe-subscription.module.ts +1 -0
  148. package/src/features/billing/stripe-usage/stripe-usage.module.ts +1 -0
  149. package/src/features/user/data/user.interface.ts +1 -0
  150. package/src/features/user/data/user.ts +6 -0
  151. package/src/features/waitlist/data/WaitlistService.ts +1 -8
  152. package/src/features/waitlist/waitlist-stats.module.ts +1 -0
  153. package/src/shadcnui/ui/resizable.tsx +33 -11
  154. package/src/unified/JsonApiRequest.ts +2 -1
  155. package/dist/AuthComponent-hxOPs9o8.d.mts +0 -11
  156. package/dist/AuthComponent-hxOPs9o8.d.ts +0 -11
  157. package/dist/JsonApiRequest-GR3L56A5.js +0 -24
  158. package/dist/chunk-BAOP6PTD.mjs.map +0 -1
  159. package/dist/chunk-FM6WRAN5.js.map +0 -1
  160. package/dist/chunk-GKY5DAIH.js.map +0 -1
  161. package/dist/chunk-GVN7XC3U.mjs.map +0 -1
  162. package/dist/chunk-ZNGEVB5M.js.map +0 -1
  163. /package/dist/{BlockNoteEditor-EKY4AHVK.mjs.map → BlockNoteEditor-B3RQ4VQ7.mjs.map} +0 -0
  164. /package/dist/{JsonApiRequest-K5BRU7RE.mjs.map → JsonApiRequest-XCQHVVYD.mjs.map} +0 -0
  165. /package/dist/{chunk-RRIYLEY6.mjs.map → chunk-LBIC4GJK.mjs.map} +0 -0
@@ -0,0 +1,175 @@
1
+ "use client";
2
+
3
+ import { ShieldAlert, ShieldCheck } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useCallback, useEffect, useState } from "react";
6
+ import { v4 } from "uuid";
7
+ import { errorToast } from "../../../../components";
8
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Separator } from "../../../../shadcnui";
9
+ import { showToast } from "../../../../utils/toast";
10
+ import { PasskeyInterface } from "../../data/passkey.interface";
11
+ import { TotpAuthenticatorInterface } from "../../data/totp-authenticator.interface";
12
+ import { TwoFactorStatusInterface } from "../../data/two-factor-status.interface";
13
+ import { TwoFactorService } from "../../data/two-factor.service";
14
+ import { BackupCodesDialog } from "./BackupCodesDialog";
15
+ import { DisableTwoFactorDialog } from "./DisableTwoFactorDialog";
16
+ import { PasskeyList } from "./PasskeyList";
17
+ import { PasskeySetupDialog } from "./PasskeySetupDialog";
18
+ import { TotpAuthenticatorList } from "./TotpAuthenticatorList";
19
+ import { TotpSetupDialog } from "./TotpSetupDialog";
20
+
21
+ export function TwoFactorSettings() {
22
+ const t = useTranslations();
23
+ const [status, setStatus] = useState<TwoFactorStatusInterface | null>(null);
24
+ const [authenticators, setAuthenticators] = useState<TotpAuthenticatorInterface[]>([]);
25
+ const [passkeys, setPasskeys] = useState<PasskeyInterface[]>([]);
26
+ const [isLoading, setIsLoading] = useState(true);
27
+ const [isEnabling, setIsEnabling] = useState(false);
28
+ const [passkeyDialogOpen, setPasskeyDialogOpen] = useState(false);
29
+
30
+ const loadStatus = useCallback(async () => {
31
+ try {
32
+ // Load status and lists in parallel
33
+ const [statusData, authenticatorsList, passkeysList] = await Promise.all([
34
+ TwoFactorService.getStatus(),
35
+ TwoFactorService.listTotpAuthenticators(),
36
+ TwoFactorService.listPasskeys(),
37
+ ]);
38
+ setStatus(statusData);
39
+ setAuthenticators(authenticatorsList);
40
+ setPasskeys(passkeysList);
41
+ } catch (error) {
42
+ errorToast({ title: t("common.errors.error"), error });
43
+ } finally {
44
+ setIsLoading(false);
45
+ }
46
+ }, [t]);
47
+
48
+ useEffect(() => {
49
+ loadStatus();
50
+ }, [loadStatus]);
51
+
52
+ const handleRefresh = () => {
53
+ loadStatus();
54
+ };
55
+
56
+ const handleEnable2FA = async () => {
57
+ setIsEnabling(true);
58
+ try {
59
+ const preferredMethod = authenticators.length > 0 ? "totp" : "passkey";
60
+ await TwoFactorService.enable({ id: v4(), preferredMethod });
61
+ showToast(t("common.success"), { description: t("auth.two_factor.enabled_success") });
62
+ await loadStatus();
63
+ } catch (error) {
64
+ errorToast({ title: t("common.errors.error"), error });
65
+ } finally {
66
+ setIsEnabling(false);
67
+ }
68
+ };
69
+
70
+ if (isLoading) {
71
+ return (
72
+ <Card>
73
+ <CardContent className="flex items-center justify-center py-8">
74
+ <p className="text-muted-foreground">{t("common.loading")}</p>
75
+ </CardContent>
76
+ </Card>
77
+ );
78
+ }
79
+
80
+ const isEnabled = status?.isEnabled ?? false;
81
+
82
+ return (
83
+ <Card>
84
+ <CardHeader>
85
+ <div className="flex items-center gap-2">
86
+ {isEnabled ? (
87
+ <ShieldCheck className="h-6 w-6 text-green-600" />
88
+ ) : (
89
+ <ShieldAlert className="h-6 w-6 text-yellow-600" />
90
+ )}
91
+ <div>
92
+ <CardTitle>{t("auth.two_factor.title")}</CardTitle>
93
+ <CardDescription>
94
+ {isEnabled ? t("auth.two_factor.enabled_description") : t("auth.two_factor.disabled_description")}
95
+ </CardDescription>
96
+ </div>
97
+ </div>
98
+ </CardHeader>
99
+ <CardContent className="space-y-6">
100
+ {/* Authenticator Apps Section */}
101
+ <div className="space-y-4">
102
+ <div className="flex items-center justify-between">
103
+ <div>
104
+ <h3 className="font-medium">{t("auth.two_factor.authenticator_apps")}</h3>
105
+ <p className="text-sm text-muted-foreground">{t("auth.two_factor.authenticator_apps_description")}</p>
106
+ </div>
107
+ <TotpSetupDialog onSuccess={handleRefresh} />
108
+ </div>
109
+ <TotpAuthenticatorList authenticators={authenticators} onDelete={handleRefresh} />
110
+ </div>
111
+
112
+ <Separator />
113
+
114
+ {/* Passkeys Section */}
115
+ <div className="space-y-4">
116
+ <div className="flex items-center justify-between">
117
+ <div>
118
+ <h3 className="font-medium">{t("auth.two_factor.passkeys")}</h3>
119
+ <p className="text-sm text-muted-foreground">{t("auth.two_factor.passkeys_description")}</p>
120
+ </div>
121
+ <Button variant="outline" onClick={() => setPasskeyDialogOpen(true)}>
122
+ {t("auth.two_factor.add_passkey")}
123
+ </Button>
124
+ </div>
125
+ <PasskeyList passkeys={passkeys} onRefresh={handleRefresh} />
126
+ </div>
127
+
128
+ <PasskeySetupDialog open={passkeyDialogOpen} onOpenChange={setPasskeyDialogOpen} onSuccess={handleRefresh} />
129
+
130
+ <Separator />
131
+
132
+ {/* Backup Codes Section */}
133
+ <div className="space-y-4">
134
+ <div className="flex items-center justify-between">
135
+ <div>
136
+ <h3 className="font-medium">{t("auth.two_factor.backup_codes")}</h3>
137
+ <p className="text-sm text-muted-foreground">{t("auth.two_factor.backup_codes_description")}</p>
138
+ </div>
139
+ <BackupCodesDialog remainingCodes={status?.backupCodesCount ?? 0} onRegenerate={handleRefresh} />
140
+ </div>
141
+ </div>
142
+
143
+ {/* Enable 2FA - shown when not enabled but methods exist */}
144
+ {!isEnabled && (authenticators.length > 0 || passkeys.length > 0) && (
145
+ <>
146
+ <Separator />
147
+ <div className="flex items-center justify-between">
148
+ <div>
149
+ <h3 className="font-medium text-green-600">{t("auth.two_factor.enable_2fa")}</h3>
150
+ <p className="text-sm text-muted-foreground">{t("auth.two_factor.enable_description")}</p>
151
+ </div>
152
+ <Button onClick={handleEnable2FA} disabled={isEnabling}>
153
+ {isEnabling ? t("common.loading") : t("auth.two_factor.enable_button")}
154
+ </Button>
155
+ </div>
156
+ </>
157
+ )}
158
+
159
+ {/* Disable 2FA */}
160
+ {isEnabled && (
161
+ <>
162
+ <Separator />
163
+ <div className="flex items-center justify-between">
164
+ <div>
165
+ <h3 className="font-medium text-destructive">{t("auth.two_factor.disable_2fa")}</h3>
166
+ <p className="text-sm text-muted-foreground">{t("auth.two_factor.disable_warning")}</p>
167
+ </div>
168
+ <DisableTwoFactorDialog onSuccess={handleRefresh} />
169
+ </div>
170
+ </>
171
+ )}
172
+ </CardContent>
173
+ </Card>
174
+ );
175
+ }
@@ -0,0 +1,9 @@
1
+ export { PasskeySetupDialog } from "./PasskeySetupDialog";
2
+ export { PasskeyList } from "./PasskeyList";
3
+ export { TotpInput } from "./TotpInput";
4
+ export { PasskeyButton } from "./PasskeyButton";
5
+ export { BackupCodesDialog } from "./BackupCodesDialog";
6
+ export { DisableTwoFactorDialog } from "./DisableTwoFactorDialog";
7
+ export { TotpSetupDialog } from "./TotpSetupDialog";
8
+ export { TotpAuthenticatorList } from "./TotpAuthenticatorList";
9
+ export { TwoFactorSettings } from "./TwoFactorSettings";
@@ -8,15 +8,19 @@ import {
8
8
  LandingComponent,
9
9
  Login,
10
10
  ResetPassword,
11
+ TwoFactorChallenge,
11
12
  } from "../components";
12
13
  import Register from "../components/forms/Register";
13
14
  import { AuthComponent } from "../enums";
15
+ import { TwoFactorChallengeInterface } from "../data/two-factor-challenge.interface";
14
16
 
15
17
  interface AuthContextType {
16
18
  activeComponent: ReactElement<any> | null;
17
19
  setComponentType: (componentType: AuthComponent) => void;
18
20
  setParams: (params?: { code?: string }) => void;
19
21
  params?: { code?: string };
22
+ pendingTwoFactor?: TwoFactorChallengeInterface;
23
+ setPendingTwoFactor: (challenge?: TwoFactorChallengeInterface) => void;
20
24
  }
21
25
 
22
26
  const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -32,6 +36,7 @@ export const AuthContextProvider = ({
32
36
  }) => {
33
37
  const [componentType, setComponentType] = useState<AuthComponent | undefined>(initialComponentType);
34
38
  const [params, setParams] = useState<{ code?: string } | undefined>(initialParams);
39
+ const [pendingTwoFactor, setPendingTwoFactor] = useState<TwoFactorChallengeInterface | undefined>();
35
40
 
36
41
  const activeComponent = useMemo(() => {
37
42
  if (componentType === undefined) return null;
@@ -49,6 +54,8 @@ export const AuthContextProvider = ({
49
54
  return <ResetPassword />;
50
55
  case AuthComponent.AcceptInvitation:
51
56
  return <AcceptInvitation />;
57
+ case AuthComponent.TwoFactorChallenge:
58
+ return <TwoFactorChallenge />;
52
59
  default:
53
60
  return <LandingComponent />;
54
61
  }
@@ -61,6 +68,8 @@ export const AuthContextProvider = ({
61
68
  setComponentType,
62
69
  setParams,
63
70
  params,
71
+ pendingTwoFactor,
72
+ setPendingTwoFactor,
64
73
  }}
65
74
  >
66
75
  {children}
@@ -11,9 +11,14 @@ import {
11
11
  import { JsonApiDelete, JsonApiGet, JsonApiPost } from "../../../unified";
12
12
  import { UserInterface } from "../../user";
13
13
  import { getTokenHandler } from "../config";
14
+ import { TwoFactorChallengeInterface } from "./two-factor-challenge.interface";
14
15
 
15
16
  export class AuthService extends AbstractService {
16
- static async login(params: { email: string; password: string; language?: string }): Promise<UserInterface> {
17
+ static async login(params: {
18
+ email: string;
19
+ password: string;
20
+ language?: string;
21
+ }): Promise<UserInterface | TwoFactorChallengeInterface> {
17
22
  const language = params.language || "en-US";
18
23
 
19
24
  const apiResponse: ApiResponseInterface = await JsonApiPost({
@@ -25,6 +30,18 @@ export class AuthService extends AbstractService {
25
30
 
26
31
  if (!apiResponse.ok) throw new Error(apiResponse.error);
27
32
 
33
+ // POLYMORPHIC RESPONSE: Login can return EITHER Auth OR TwoFactorChallenge
34
+ // translateResponse used Modules.Auth for rehydration, but backend may return different type
35
+ // Check raw response type and manually rehydrate with correct module if needed
36
+ if (apiResponse.raw?.data?.type === "two-factor-challenge") {
37
+ const challenge = rehydrate<TwoFactorChallengeInterface>(Modules.TwoFactorChallenge, {
38
+ jsonApi: apiResponse.raw.data,
39
+ included: apiResponse.raw.included ?? [],
40
+ });
41
+ return challenge;
42
+ }
43
+
44
+ // Normal auth response
28
45
  const auth = apiResponse.data as AuthInterface;
29
46
 
30
47
  // Use injected token handler if configured
@@ -0,0 +1,20 @@
1
+ import { AbstractApiData } from "../../../core";
2
+
3
+ export type BackupCodeVerifyInput = {
4
+ id: string;
5
+ code: string;
6
+ };
7
+
8
+ export class BackupCodeVerify extends AbstractApiData {
9
+ createJsonApi(data: BackupCodeVerifyInput) {
10
+ return {
11
+ data: {
12
+ type: "backup-codes",
13
+ id: data.id,
14
+ attributes: {
15
+ code: data.code,
16
+ },
17
+ },
18
+ };
19
+ }
20
+ }
@@ -1,3 +1,24 @@
1
1
  export * from "./auth";
2
2
  export * from "./auth.interface";
3
3
  export * from "./auth.service";
4
+ export * from "./two-factor-status.interface";
5
+ export * from "./two-factor-status";
6
+ export * from "./totp-authenticator.interface";
7
+ export * from "./totp-authenticator";
8
+ export * from "./totp-setup.interface";
9
+ export * from "./totp-setup";
10
+ export * from "./passkey.interface";
11
+ export * from "./passkey";
12
+ export * from "./passkey-registration-options";
13
+ export * from "./passkey-registration-verify";
14
+ export * from "./passkey-authentication-options.interface";
15
+ export * from "./passkey-authentication-options";
16
+ export * from "./two-factor-challenge.interface";
17
+ export * from "./two-factor.service";
18
+ export * from "./totp-verify";
19
+ export * from "./totp-verify-login";
20
+ export * from "./passkey-rename";
21
+ export * from "./passkey-verify-login";
22
+ export * from "./two-factor-enable";
23
+ export * from "./two-factor-challenge";
24
+ export * from "./backup-code-verify";
@@ -0,0 +1,7 @@
1
+ import { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/browser";
2
+ import { ApiDataInterface } from "../../../core";
3
+
4
+ export interface PasskeyAuthenticationOptionsInterface extends ApiDataInterface {
5
+ get pendingId(): string;
6
+ get options(): PublicKeyCredentialRequestOptionsJSON;
7
+ }
@@ -0,0 +1,37 @@
1
+ import { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/browser";
2
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
3
+ import { PasskeyAuthenticationOptionsInterface } from "./passkey-authentication-options.interface";
4
+
5
+ export type PasskeyAuthenticationOptionsInput = {
6
+ id: string;
7
+ };
8
+
9
+ export class PasskeyAuthenticationOptions extends AbstractApiData implements PasskeyAuthenticationOptionsInterface {
10
+ private _pendingId: string = "";
11
+ private _options: PublicKeyCredentialRequestOptionsJSON = {} as PublicKeyCredentialRequestOptionsJSON;
12
+
13
+ get pendingId(): string {
14
+ return this._pendingId;
15
+ }
16
+
17
+ get options(): PublicKeyCredentialRequestOptionsJSON {
18
+ return this._options;
19
+ }
20
+
21
+ rehydrate(data: JsonApiHydratedDataInterface): this {
22
+ super.rehydrate(data);
23
+ this._pendingId = data.jsonApi.attributes.pendingId ?? this.id ?? "";
24
+ this._options = data.jsonApi.attributes.options ?? {};
25
+ return this;
26
+ }
27
+
28
+ createJsonApi(data: PasskeyAuthenticationOptionsInput) {
29
+ return {
30
+ data: {
31
+ type: "passkey-authentication-options",
32
+ id: data.id,
33
+ attributes: {},
34
+ },
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,46 @@
1
+ import { PublicKeyCredentialCreationOptionsJSON } from "@simplewebauthn/browser";
2
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
3
+
4
+ export type PasskeyRegistrationOptionsInput = {
5
+ id: string;
6
+ userName: string;
7
+ userDisplayName?: string;
8
+ };
9
+
10
+ export interface PasskeyRegistrationOptionsInterface {
11
+ pendingId: string;
12
+ options: PublicKeyCredentialCreationOptionsJSON;
13
+ }
14
+
15
+ export class PasskeyRegistrationOptions extends AbstractApiData implements PasskeyRegistrationOptionsInterface {
16
+ private _pendingId: string = "";
17
+ private _options: PublicKeyCredentialCreationOptionsJSON = {} as PublicKeyCredentialCreationOptionsJSON;
18
+
19
+ get pendingId(): string {
20
+ return this._pendingId;
21
+ }
22
+
23
+ get options(): PublicKeyCredentialCreationOptionsJSON {
24
+ return this._options;
25
+ }
26
+
27
+ rehydrate(data: JsonApiHydratedDataInterface): this {
28
+ super.rehydrate(data);
29
+ this._pendingId = data.jsonApi.attributes.pendingId ?? this.id;
30
+ this._options = data.jsonApi.attributes.options ?? {};
31
+ return this;
32
+ }
33
+
34
+ createJsonApi(data: PasskeyRegistrationOptionsInput) {
35
+ return {
36
+ data: {
37
+ type: "passkeys",
38
+ id: data.id,
39
+ attributes: {
40
+ userName: data.userName,
41
+ userDisplayName: data.userDisplayName,
42
+ },
43
+ },
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,62 @@
1
+ import { RegistrationResponseJSON } from "@simplewebauthn/browser";
2
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
3
+ import { PasskeyInterface } from "./passkey.interface";
4
+
5
+ export type PasskeyRegistrationVerifyInput = {
6
+ id: string;
7
+ pendingId: string;
8
+ name: string;
9
+ response: RegistrationResponseJSON;
10
+ };
11
+
12
+ export class PasskeyRegistrationVerify extends AbstractApiData implements PasskeyInterface {
13
+ private _name: string = "";
14
+ private _credentialId: string = "";
15
+ private _deviceType: string = "";
16
+ private _backedUp: boolean = false;
17
+ private _lastUsedAt?: Date;
18
+
19
+ get name(): string {
20
+ return this._name;
21
+ }
22
+
23
+ get credentialId(): string {
24
+ return this._credentialId;
25
+ }
26
+
27
+ get deviceType(): string {
28
+ return this._deviceType;
29
+ }
30
+
31
+ get backedUp(): boolean {
32
+ return this._backedUp;
33
+ }
34
+
35
+ get lastUsedAt(): Date | undefined {
36
+ return this._lastUsedAt;
37
+ }
38
+
39
+ rehydrate(data: JsonApiHydratedDataInterface): this {
40
+ super.rehydrate(data);
41
+ this._name = data.jsonApi.attributes.name ?? "";
42
+ this._credentialId = data.jsonApi.attributes.credentialId ?? "";
43
+ this._deviceType = data.jsonApi.attributes.deviceType ?? "";
44
+ this._backedUp = data.jsonApi.attributes.backedUp ?? false;
45
+ this._lastUsedAt = data.jsonApi.attributes.lastUsedAt ? new Date(data.jsonApi.attributes.lastUsedAt) : undefined;
46
+ return this;
47
+ }
48
+
49
+ createJsonApi(data: PasskeyRegistrationVerifyInput) {
50
+ return {
51
+ data: {
52
+ type: "passkeys",
53
+ id: data.id,
54
+ attributes: {
55
+ pendingId: data.pendingId,
56
+ name: data.name,
57
+ response: data.response,
58
+ },
59
+ },
60
+ };
61
+ }
62
+ }
@@ -0,0 +1,20 @@
1
+ import { AbstractApiData } from "../../../core";
2
+
3
+ export type PasskeyRenameInput = {
4
+ id: string;
5
+ name: string;
6
+ };
7
+
8
+ export class PasskeyRename extends AbstractApiData {
9
+ createJsonApi(data: PasskeyRenameInput) {
10
+ return {
11
+ data: {
12
+ type: "passkeys",
13
+ id: data.id,
14
+ attributes: {
15
+ name: data.name,
16
+ },
17
+ },
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ import { AuthenticationResponseJSON } from "@simplewebauthn/browser";
2
+ import { AbstractApiData } from "../../../core";
3
+
4
+ export type PasskeyVerifyLoginInput = {
5
+ id: string;
6
+ pendingId: string;
7
+ response: AuthenticationResponseJSON;
8
+ };
9
+
10
+ export class PasskeyVerifyLogin extends AbstractApiData {
11
+ createJsonApi(data: PasskeyVerifyLoginInput) {
12
+ return {
13
+ data: {
14
+ type: "passkeys",
15
+ id: data.id,
16
+ attributes: {
17
+ pendingId: data.pendingId,
18
+ response: data.response,
19
+ },
20
+ },
21
+ };
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface PasskeyInterface extends ApiDataInterface {
4
+ get name(): string;
5
+ get credentialId(): string;
6
+ get deviceType(): string;
7
+ get backedUp(): boolean;
8
+ get lastUsedAt(): Date | undefined;
9
+ }
@@ -0,0 +1,40 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { PasskeyInterface } from "./passkey.interface";
3
+
4
+ export class Passkey extends AbstractApiData implements PasskeyInterface {
5
+ private _name: string = "";
6
+ private _credentialId: string = "";
7
+ private _deviceType: string = "";
8
+ private _backedUp: boolean = false;
9
+ private _lastUsedAt?: Date;
10
+
11
+ get name(): string {
12
+ return this._name;
13
+ }
14
+
15
+ get credentialId(): string {
16
+ return this._credentialId;
17
+ }
18
+
19
+ get deviceType(): string {
20
+ return this._deviceType;
21
+ }
22
+
23
+ get backedUp(): boolean {
24
+ return this._backedUp;
25
+ }
26
+
27
+ get lastUsedAt(): Date | undefined {
28
+ return this._lastUsedAt;
29
+ }
30
+
31
+ rehydrate(data: JsonApiHydratedDataInterface): this {
32
+ super.rehydrate(data);
33
+ this._name = data.jsonApi.attributes.name ?? "";
34
+ this._credentialId = data.jsonApi.attributes.credentialId ?? "";
35
+ this._deviceType = data.jsonApi.attributes.deviceType ?? "";
36
+ this._backedUp = data.jsonApi.attributes.backedUp ?? false;
37
+ this._lastUsedAt = data.jsonApi.attributes.lastUsedAt ? new Date(data.jsonApi.attributes.lastUsedAt) : undefined;
38
+ return this;
39
+ }
40
+ }
@@ -0,0 +1,7 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface TotpAuthenticatorInterface extends ApiDataInterface {
4
+ get name(): string;
5
+ get verified(): boolean;
6
+ get lastUsedAt(): Date | undefined;
7
+ }
@@ -0,0 +1,28 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { TotpAuthenticatorInterface } from "./totp-authenticator.interface";
3
+
4
+ export class TotpAuthenticator extends AbstractApiData implements TotpAuthenticatorInterface {
5
+ private _name: string = "";
6
+ private _verified: boolean = false;
7
+ private _lastUsedAt?: Date;
8
+
9
+ get name(): string {
10
+ return this._name;
11
+ }
12
+
13
+ get verified(): boolean {
14
+ return this._verified;
15
+ }
16
+
17
+ get lastUsedAt(): Date | undefined {
18
+ return this._lastUsedAt;
19
+ }
20
+
21
+ rehydrate(data: JsonApiHydratedDataInterface): this {
22
+ super.rehydrate(data);
23
+ this._name = data.jsonApi.attributes.name ?? "";
24
+ this._verified = data.jsonApi.attributes.verified ?? false;
25
+ this._lastUsedAt = data.jsonApi.attributes.lastUsedAt ? new Date(data.jsonApi.attributes.lastUsedAt) : undefined;
26
+ return this;
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export interface TotpSetupInterface {
2
+ get secret(): string;
3
+ get qrCodeUri(): string;
4
+ get authenticatorId(): string;
5
+ }
@@ -0,0 +1,48 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { TotpSetupInterface } from "./totp-setup.interface";
3
+
4
+ export type TotpSetupInput = {
5
+ id: string;
6
+ name: string;
7
+ accountName: string;
8
+ };
9
+
10
+ export class TotpSetup extends AbstractApiData implements TotpSetupInterface {
11
+ private _qrCodeUri: string = "";
12
+ private _secret: string = "";
13
+ private _authenticatorId: string = "";
14
+
15
+ get qrCodeUri(): string {
16
+ return this._qrCodeUri;
17
+ }
18
+
19
+ get secret(): string {
20
+ return this._secret;
21
+ }
22
+
23
+ get authenticatorId(): string {
24
+ return this._authenticatorId;
25
+ }
26
+
27
+ rehydrate(data: JsonApiHydratedDataInterface): this {
28
+ super.rehydrate(data);
29
+ this._qrCodeUri = data.jsonApi.attributes.qrCodeUri ?? "";
30
+ this._secret = data.jsonApi.attributes.secret ?? "";
31
+ // authenticatorId is the entity ID, not an attribute
32
+ this._authenticatorId = data.jsonApi.id ?? "";
33
+ return this;
34
+ }
35
+
36
+ createJsonApi(data: TotpSetupInput) {
37
+ return {
38
+ data: {
39
+ type: "totp-authenticators",
40
+ id: data.id,
41
+ attributes: {
42
+ name: data.name,
43
+ accountName: data.accountName,
44
+ },
45
+ },
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,20 @@
1
+ import { AbstractApiData } from "../../../core";
2
+
3
+ export type TotpVerifyLoginInput = {
4
+ id: string;
5
+ code: string;
6
+ };
7
+
8
+ export class TotpVerifyLogin extends AbstractApiData {
9
+ createJsonApi(data: TotpVerifyLoginInput) {
10
+ return {
11
+ data: {
12
+ type: "totp-authenticators",
13
+ id: data.id,
14
+ attributes: {
15
+ code: data.code,
16
+ },
17
+ },
18
+ };
19
+ }
20
+ }