@object-ui/auth 13.1.0 → 14.0.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/CHANGELOG.md +58 -0
- package/dist/AuthContext.d.ts +10 -0
- package/dist/AuthContext.d.ts.map +1 -1
- package/dist/AuthProvider.d.ts.map +1 -1
- package/dist/AuthProvider.js +74 -0
- package/dist/ForgotPasswordForm.d.ts +26 -0
- package/dist/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/ForgotPasswordForm.js +104 -3
- package/dist/LoginForm.d.ts +20 -0
- package/dist/LoginForm.d.ts.map +1 -1
- package/dist/LoginForm.js +101 -3
- package/dist/SocialSignInButtons.d.ts.map +1 -1
- package/dist/SocialSignInButtons.js +12 -1
- package/dist/createAuthClient.d.ts.map +1 -1
- package/dist/createAuthClient.js +76 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/phone-identifier.d.ts +20 -0
- package/dist/phone-identifier.d.ts.map +1 -0
- package/dist/phone-identifier.js +26 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/useAuth.d.ts.map +1 -1
- package/dist/useAuth.js +5 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @object-ui/auth
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 94d00d4: feat(auth): phone number + password sign-in on the login page
|
|
8
|
+
|
|
9
|
+
The login page's password mode now accepts an **email OR a phone number** as the
|
|
10
|
+
identifier and routes by shape — email → `/sign-in/email`, phone →
|
|
11
|
+
`/sign-in/phone-number` (better-auth phoneNumber plugin, framework#2780). It
|
|
12
|
+
coexists with the existing phone-OTP mode.
|
|
13
|
+
|
|
14
|
+
- Gated on `features.phoneNumber` (phoneNumber plugin enabled). Unlike phone-OTP
|
|
15
|
+
it needs no SMS service, so it uses that coarser capability flag, not
|
|
16
|
+
`features.phoneNumberOtp`. When the flag is off the field stays email-only.
|
|
17
|
+
- New `AuthClient.signInWithPhonePassword(phoneNumber, password)` wired through
|
|
18
|
+
`AuthContext` / `AuthProvider` / `useAuth`.
|
|
19
|
+
- New `normalizePhoneIdentifier` / `looksLikePhoneIdentifier` helpers that mirror
|
|
20
|
+
the backend's `normalizePhoneNumber` exactly (strip `[\s\-().]`, validate
|
|
21
|
+
`^\+?[0-9]{6,15}$`, **no** forced E.164 / country code — the backend stores the
|
|
22
|
+
light-stripped form, so anything heavier would break the lookup).
|
|
23
|
+
- SSO stays email-only (a phone-shaped identifier no longer attempts domain
|
|
24
|
+
routing).
|
|
25
|
+
|
|
26
|
+
Only works for accounts that have both a phone number and a password set;
|
|
27
|
+
phone-only accounts set a password on first OTP sign-in.
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [86c69c3]
|
|
32
|
+
- Updated dependencies [6a74160]
|
|
33
|
+
- @object-ui/types@14.0.0
|
|
34
|
+
|
|
35
|
+
## 13.2.0
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 53c40c2: feat: identity import — the stock ImportWizard now drives sys_user bulk import (framework#2782)
|
|
40
|
+
|
|
41
|
+
The Users list gets an Import entry for platform admins (gated on
|
|
42
|
+
`features.admin` from `/api/v1/auth/config` plus workspace-admin), wired to
|
|
43
|
+
the dedicated `POST /api/v1/auth/admin/import-users` pipeline instead of the
|
|
44
|
+
generic data import (which would bypass better-auth hashing and produce
|
|
45
|
+
accounts that can never sign in).
|
|
46
|
+
|
|
47
|
+
- **plugin-grid**: two generic, backend-agnostic ImportWizard slots —
|
|
48
|
+
`extraOptionsContent` (host-injected options on the preview step) and
|
|
49
|
+
`renderResultExtra` (host-rendered content on the result step).
|
|
50
|
+
- **app-shell**: identity import dataSource adapter — splits files into the
|
|
51
|
+
endpoint's ≤500-row batches (idempotent upsert makes re-runs safe), injects
|
|
52
|
+
the selected password policy, renumbers per-batch results onto the whole
|
|
53
|
+
file, and enriches rows with their sign-in identity. Password policy panel
|
|
54
|
+
(`none` default / `invite` / `temporary`) and a one-shot temporary-password
|
|
55
|
+
reveal with CSV download (client memory only — nothing is persisted).
|
|
56
|
+
Async-job/undo surfaces are hidden for identity import by design.
|
|
57
|
+
- **auth**: `AuthPublicConfig.features.admin` typing.
|
|
58
|
+
- **i18n**: en/zh strings for the identity import panels.
|
|
59
|
+
- @object-ui/types@13.2.0
|
|
60
|
+
|
|
3
61
|
## 13.1.0
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
package/dist/AuthContext.d.ts
CHANGED
|
@@ -47,6 +47,16 @@ export interface AuthContextValue {
|
|
|
47
47
|
sendVerificationEmail: (email: string, callbackURL?: string) => Promise<void>;
|
|
48
48
|
/** Reset password with token */
|
|
49
49
|
resetPassword: (token: string, newPassword: string) => Promise<void>;
|
|
50
|
+
/** framework#2780 — request a sign-in OTP SMS for the phone number. */
|
|
51
|
+
sendPhoneOtp: (phoneNumber: string) => Promise<void>;
|
|
52
|
+
/** framework#2780 — sign in by verifying a phone OTP. */
|
|
53
|
+
signInWithPhoneOtp: (phoneNumber: string, code: string) => Promise<void>;
|
|
54
|
+
/** framework#2780 — sign in with phone number + password (no SMS required). */
|
|
55
|
+
signInWithPhonePassword: (phoneNumber: string, password: string) => Promise<void>;
|
|
56
|
+
/** framework#2780 — request a password-reset OTP SMS for the phone number. */
|
|
57
|
+
requestPhonePasswordReset: (phoneNumber: string) => Promise<void>;
|
|
58
|
+
/** framework#2780 — reset the password with a phone OTP. */
|
|
59
|
+
resetPasswordWithPhoneOtp: (phoneNumber: string, otp: string, newPassword: string) => Promise<void>;
|
|
50
60
|
/**
|
|
51
61
|
* Change the local (email/password) password. Requires the current
|
|
52
62
|
* password. Use this when `hasLocalPassword()` returns true.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../src/AuthContext.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../src/AuthContext.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAoD,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAwB,kBAAkB,EAAE,kBAAkB,EAAyG,MAAM,SAAS,CAAC;AAKnM,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EAAE,cAAc,EACtB,iBAAiB,EACjB,OAAc,EACd,WAAW,EACX,QAAQ,GACT,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAoD,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAwB,kBAAkB,EAAE,kBAAkB,EAAyG,MAAM,SAAS,CAAC;AAKnM,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EAAE,cAAc,EACtB,iBAAiB,EACjB,OAAc,EACd,WAAW,EACX,QAAQ,GACT,EAAE,iBAAiB,qBA6oBnB"}
|
package/dist/AuthProvider.js
CHANGED
|
@@ -230,6 +230,74 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
230
230
|
throw authError;
|
|
231
231
|
}
|
|
232
232
|
}, [client]);
|
|
233
|
+
// --- Phone-number OTP (framework#2780) --------------------------------
|
|
234
|
+
const sendPhoneOtp = useCallback(async (phoneNumber) => {
|
|
235
|
+
setError(null);
|
|
236
|
+
try {
|
|
237
|
+
await client.sendPhoneOtp(phoneNumber);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
241
|
+
setError(authError);
|
|
242
|
+
throw authError;
|
|
243
|
+
}
|
|
244
|
+
}, [client]);
|
|
245
|
+
const signInWithPhoneOtp = useCallback(async (phoneNumber, code) => {
|
|
246
|
+
setIsLoading(true);
|
|
247
|
+
setError(null);
|
|
248
|
+
try {
|
|
249
|
+
const result = await client.signInWithPhoneOtp(phoneNumber, code);
|
|
250
|
+
setUser(result.user);
|
|
251
|
+
setSession(result.session);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
255
|
+
setError(authError);
|
|
256
|
+
throw authError;
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
setIsLoading(false);
|
|
260
|
+
}
|
|
261
|
+
}, [client]);
|
|
262
|
+
const signInWithPhonePassword = useCallback(async (phoneNumber, password) => {
|
|
263
|
+
setIsLoading(true);
|
|
264
|
+
setError(null);
|
|
265
|
+
try {
|
|
266
|
+
const result = await client.signInWithPhonePassword(phoneNumber, password);
|
|
267
|
+
setUser(result.user);
|
|
268
|
+
setSession(result.session);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
272
|
+
setError(authError);
|
|
273
|
+
throw authError;
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
setIsLoading(false);
|
|
277
|
+
}
|
|
278
|
+
}, [client]);
|
|
279
|
+
const requestPhonePasswordReset = useCallback(async (phoneNumber) => {
|
|
280
|
+
setError(null);
|
|
281
|
+
try {
|
|
282
|
+
await client.requestPhonePasswordReset(phoneNumber);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
286
|
+
setError(authError);
|
|
287
|
+
throw authError;
|
|
288
|
+
}
|
|
289
|
+
}, [client]);
|
|
290
|
+
const resetPasswordWithPhoneOtp = useCallback(async (phoneNumber, otp, newPassword) => {
|
|
291
|
+
setError(null);
|
|
292
|
+
try {
|
|
293
|
+
await client.resetPasswordWithPhoneOtp(phoneNumber, otp, newPassword);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
297
|
+
setError(authError);
|
|
298
|
+
throw authError;
|
|
299
|
+
}
|
|
300
|
+
}, [client]);
|
|
233
301
|
// ADR-0069 — the API fetch interceptor emits an auth-policy gate; raise the
|
|
234
302
|
// remediation overlay. Don't override an already-shown gate.
|
|
235
303
|
useEffect(() => {
|
|
@@ -457,6 +525,11 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
457
525
|
forgotPassword,
|
|
458
526
|
sendVerificationEmail,
|
|
459
527
|
resetPassword,
|
|
528
|
+
sendPhoneOtp,
|
|
529
|
+
signInWithPhoneOtp,
|
|
530
|
+
signInWithPhonePassword,
|
|
531
|
+
requestPhonePasswordReset,
|
|
532
|
+
resetPasswordWithPhoneOtp,
|
|
460
533
|
changePassword,
|
|
461
534
|
remediationRequired,
|
|
462
535
|
setRemediationRequired,
|
|
@@ -489,6 +562,7 @@ export function AuthProvider({ authUrl, client: externalClient, onAuthStateChang
|
|
|
489
562
|
}), [
|
|
490
563
|
user, session, isAuthenticated, isAuthEnabled, isLoading, error, isPreviewMode, previewMode,
|
|
491
564
|
signIn, signUp, signOut, updateUser, forgotPassword, sendVerificationEmail, resetPassword, changePassword, setInitialPassword, hasLocalPassword, getAuthConfig, signInWithProvider,
|
|
565
|
+
sendPhoneOtp, signInWithPhoneOtp, signInWithPhonePassword, requestPhonePasswordReset, resetPasswordWithPhoneOtp,
|
|
492
566
|
remediationRequired, enrollTotp, verifyTotp,
|
|
493
567
|
organizations, activeOrganization, activeMember, isOrganizationsLoading, switchOrganization, createOrganization, refreshOrganizations,
|
|
494
568
|
updateOrganization, deleteOrganization, leaveOrganization,
|
|
@@ -18,6 +18,32 @@ export interface ForgotPasswordFormLabels {
|
|
|
18
18
|
backToSignInText?: string;
|
|
19
19
|
rememberPasswordText?: string;
|
|
20
20
|
signInText?: string;
|
|
21
|
+
/** Phone reset (framework#2780) — phone field label (defaults to "Phone number"). */
|
|
22
|
+
phoneLabel?: string;
|
|
23
|
+
/** Phone reset — phone field placeholder (defaults to "+1 555 000 0000"). */
|
|
24
|
+
phonePlaceholder?: string;
|
|
25
|
+
/** Phone reset — code field label (defaults to "Verification code"). */
|
|
26
|
+
otpCodeLabel?: string;
|
|
27
|
+
/** Phone reset — code field placeholder (defaults to "6-digit code"). */
|
|
28
|
+
otpCodePlaceholder?: string;
|
|
29
|
+
/** Phone reset — "send code" button (defaults to "Get code"). */
|
|
30
|
+
sendOtpButton?: string;
|
|
31
|
+
/** Phone reset — resend countdown, `{seconds}` hole (defaults to "Resend in {seconds}s"). */
|
|
32
|
+
resendOtpCountdownText?: string;
|
|
33
|
+
/** Phone reset — new password field label (defaults to "New password"). */
|
|
34
|
+
newPasswordLabel?: string;
|
|
35
|
+
/** Phone reset — new password placeholder (defaults to "Enter a new password"). */
|
|
36
|
+
newPasswordPlaceholder?: string;
|
|
37
|
+
/** Phone reset — submit button (defaults to "Reset Password"). */
|
|
38
|
+
resetButton?: string;
|
|
39
|
+
/** Link that switches to the phone (SMS code) branch (defaults to "Reset via SMS code"). */
|
|
40
|
+
usePhoneResetText?: string;
|
|
41
|
+
/** Link that switches back to the email branch (defaults to "Reset via email instead"). */
|
|
42
|
+
useEmailResetText?: string;
|
|
43
|
+
/** Phone reset — success screen title (defaults to "Password reset"). */
|
|
44
|
+
phoneSuccessTitle?: string;
|
|
45
|
+
/** Phone reset — success screen description. */
|
|
46
|
+
phoneSuccessDescription?: string;
|
|
21
47
|
}
|
|
22
48
|
export interface ForgotPasswordFormProps {
|
|
23
49
|
/** Callback on successful submission */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../src/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"ForgotPasswordForm.d.ts","sourceRoot":"","sources":["../src/ForgotPasswordForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAYtD,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,wBAAwB;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC5D,uCAAuC;IACvC,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,OAAO,EACP,QAAmB,EACnB,KAA6B,EAC7B,WAAyF,EACzF,IAAI,EACJ,QAAgB,EAChB,aAAa,EAAE,QAAsB,EACrC,MAAW,GACZ,EAAE,uBAAuB,qBAyUzB"}
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
import { useState } from 'react';
|
|
9
|
+
import { useEffect, useState } from 'react';
|
|
10
10
|
import { useAuth } from './useAuth';
|
|
11
11
|
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthErrorBanner, AuthFormHeader, AuthMailIcon, AuthSpinner, } from './authStyles';
|
|
12
12
|
const DefaultLink = ({ href, className, children }) => (_jsx("a", { href: href, className: className, children: children }));
|
|
@@ -25,10 +25,46 @@ const DefaultKeyIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none",
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export function ForgotPasswordForm({ onSuccess, onError, loginUrl = '/login', title = 'Reset your password', description = "Enter your email address and we'll send you a link to reset your password", icon, hideIcon = false, linkComponent: LinkComp = DefaultLink, labels = {}, }) {
|
|
28
|
-
const { forgotPassword, isLoading } = useAuth();
|
|
28
|
+
const { forgotPassword, isLoading, getAuthConfig, requestPhonePasswordReset, resetPasswordWithPhoneOtp, } = useAuth();
|
|
29
29
|
const [email, setEmail] = useState('');
|
|
30
30
|
const [error, setError] = useState(null);
|
|
31
31
|
const [submitted, setSubmitted] = useState(false);
|
|
32
|
+
// Phone (SMS OTP) reset branch (framework#2780). Only offered when the
|
|
33
|
+
// server reports `features.phoneNumberOtp` — the phoneNumber plugin plus a
|
|
34
|
+
// deliverable SMS service — so the link never leads to codes that can't
|
|
35
|
+
// arrive.
|
|
36
|
+
const [phoneOtpEnabled, setPhoneOtpEnabled] = useState(false);
|
|
37
|
+
const [mode, setMode] = useState('email');
|
|
38
|
+
const [phone, setPhone] = useState('');
|
|
39
|
+
const [otpCode, setOtpCode] = useState('');
|
|
40
|
+
const [newPassword, setNewPassword] = useState('');
|
|
41
|
+
const [otpSending, setOtpSending] = useState(false);
|
|
42
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
43
|
+
const [otpCooldown, setOtpCooldown] = useState(0);
|
|
44
|
+
const [phoneResetDone, setPhoneResetDone] = useState(false);
|
|
45
|
+
const [resetting, setResetting] = useState(false);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
Promise.resolve()
|
|
49
|
+
.then(() => getAuthConfig())
|
|
50
|
+
.then((config) => {
|
|
51
|
+
if (!cancelled)
|
|
52
|
+
setPhoneOtpEnabled(config?.features?.phoneNumberOtp === true);
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {
|
|
55
|
+
// Phone reset is an enhancement — a failed config fetch just hides it.
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
};
|
|
60
|
+
}, [getAuthConfig]);
|
|
61
|
+
// Tick the resend cooldown down once per second while it's armed.
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (otpCooldown <= 0)
|
|
64
|
+
return;
|
|
65
|
+
const timer = setTimeout(() => setOtpCooldown((s) => (s > 1 ? s - 1 : 0)), 1000);
|
|
66
|
+
return () => clearTimeout(timer);
|
|
67
|
+
}, [otpCooldown]);
|
|
32
68
|
const l = {
|
|
33
69
|
emailLabel: labels.emailLabel ?? 'Email',
|
|
34
70
|
emailPlaceholder: labels.emailPlaceholder ?? 'name@example.com',
|
|
@@ -40,6 +76,20 @@ export function ForgotPasswordForm({ onSuccess, onError, loginUrl = '/login', ti
|
|
|
40
76
|
backToSignInText: labels.backToSignInText ?? 'Back to sign in',
|
|
41
77
|
rememberPasswordText: labels.rememberPasswordText ?? 'Remember your password?',
|
|
42
78
|
signInText: labels.signInText ?? 'Sign in',
|
|
79
|
+
phoneLabel: labels.phoneLabel ?? 'Phone number',
|
|
80
|
+
phonePlaceholder: labels.phonePlaceholder ?? '+1 555 000 0000',
|
|
81
|
+
otpCodeLabel: labels.otpCodeLabel ?? 'Verification code',
|
|
82
|
+
otpCodePlaceholder: labels.otpCodePlaceholder ?? '6-digit code',
|
|
83
|
+
sendOtpButton: labels.sendOtpButton ?? 'Get code',
|
|
84
|
+
resendOtpCountdownText: labels.resendOtpCountdownText ?? 'Resend in {seconds}s',
|
|
85
|
+
newPasswordLabel: labels.newPasswordLabel ?? 'New password',
|
|
86
|
+
newPasswordPlaceholder: labels.newPasswordPlaceholder ?? 'Enter a new password',
|
|
87
|
+
resetButton: labels.resetButton ?? 'Reset Password',
|
|
88
|
+
usePhoneResetText: labels.usePhoneResetText ?? 'Reset via SMS code',
|
|
89
|
+
useEmailResetText: labels.useEmailResetText ?? 'Reset via email instead',
|
|
90
|
+
phoneSuccessTitle: labels.phoneSuccessTitle ?? 'Password reset',
|
|
91
|
+
phoneSuccessDescription: labels.phoneSuccessDescription ??
|
|
92
|
+
'Your password has been reset. You can now sign in with your new password.',
|
|
43
93
|
};
|
|
44
94
|
const handleSubmit = async (e) => {
|
|
45
95
|
e.preventDefault();
|
|
@@ -55,11 +105,62 @@ export function ForgotPasswordForm({ onSuccess, onError, loginUrl = '/login', ti
|
|
|
55
105
|
onError?.(authError);
|
|
56
106
|
}
|
|
57
107
|
};
|
|
108
|
+
/**
|
|
109
|
+
* Request a reset OTP for the entered number (framework#2780). The server
|
|
110
|
+
* always answers success (no account-existence oracle); a 429 from the
|
|
111
|
+
* per-number cooldown surfaces its honest "retry in Ns" message.
|
|
112
|
+
*/
|
|
113
|
+
const handleSendOtp = async () => {
|
|
114
|
+
if (!phone.trim() || otpSending || otpCooldown > 0)
|
|
115
|
+
return;
|
|
116
|
+
setError(null);
|
|
117
|
+
setOtpSending(true);
|
|
118
|
+
try {
|
|
119
|
+
await requestPhonePasswordReset(phone.trim());
|
|
120
|
+
setOtpSent(true);
|
|
121
|
+
setOtpCooldown(60);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
125
|
+
setError(authError.message);
|
|
126
|
+
onError?.(authError);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
setOtpSending(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handlePhoneReset = async (e) => {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
setError(null);
|
|
135
|
+
setResetting(true);
|
|
136
|
+
try {
|
|
137
|
+
await resetPasswordWithPhoneOtp(phone.trim(), otpCode.trim(), newPassword);
|
|
138
|
+
setPhoneResetDone(true);
|
|
139
|
+
onSuccess?.();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
143
|
+
setError(authError.message);
|
|
144
|
+
onError?.(authError);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
setResetting(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const switchMode = (next) => {
|
|
151
|
+
setMode(next);
|
|
152
|
+
setError(null);
|
|
153
|
+
};
|
|
154
|
+
if (phoneResetDone) {
|
|
155
|
+
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: _jsx("span", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500/10 text-emerald-600 ring-1 ring-emerald-500/15 dark:text-emerald-400", children: _jsx(AuthMailIcon, {}) }), title: l.phoneSuccessTitle, description: l.phoneSuccessDescription }), loginUrl && (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: _jsxs(LinkComp, { href: loginUrl, className: AUTH_LINK_CLASS, children: ["\u2190 ", l.backToSignInText] }) }))] }));
|
|
156
|
+
}
|
|
58
157
|
if (submitted) {
|
|
59
158
|
const successMsg = l.successDescription.includes('{{email}}')
|
|
60
159
|
? l.successDescription.replace('{{email}}', email)
|
|
61
160
|
: `${l.successDescription} ${email}`;
|
|
62
161
|
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: _jsx("span", { className: "flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-500/10 text-emerald-600 ring-1 ring-emerald-500/15 dark:text-emerald-400", children: _jsx(AuthMailIcon, {}) }), title: l.successTitle, description: successMsg }), loginUrl && (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: _jsxs(LinkComp, { href: loginUrl, className: AUTH_LINK_CLASS, children: ["\u2190 ", l.backToSignInText] }) }))] }));
|
|
63
162
|
}
|
|
64
|
-
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: hideIcon ? undefined : (icon ?? _jsx(DefaultKeyIcon, {})), title: title, description: description }), _jsxs("form", { onSubmit:
|
|
163
|
+
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: hideIcon ? undefined : (icon ?? _jsx(DefaultKeyIcon, {})), title: title, description: description }), mode === 'phone' ? (_jsxs("form", { onSubmit: handlePhoneReset, className: "space-y-4", children: [error && _jsx(AuthErrorBanner, { message: error }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "forgot-phone", className: AUTH_FIELD_LABEL_CLASS, children: l.phoneLabel }), _jsx("input", { id: "forgot-phone", type: "tel", placeholder: l.phonePlaceholder, value: phone, onChange: (e) => setPhone(e.target.value), required: true, autoComplete: "tel", disabled: resetting, className: AUTH_INPUT_CLASS })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "forgot-otp", className: AUTH_FIELD_LABEL_CLASS, children: l.otpCodeLabel }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { id: "forgot-otp", type: "text", inputMode: "numeric", placeholder: l.otpCodePlaceholder, value: otpCode, onChange: (e) => setOtpCode(e.target.value), required: true, autoComplete: "one-time-code", disabled: resetting, className: AUTH_INPUT_CLASS }), _jsxs("button", { type: "button", onClick: handleSendOtp, disabled: otpSending || otpCooldown > 0 || !phone.trim() || resetting, className: "shrink-0 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium transition-colors hover:bg-accent disabled:opacity-50", children: [otpSending && _jsx(AuthSpinner, {}), otpCooldown > 0
|
|
164
|
+
? l.resendOtpCountdownText.replace('{seconds}', String(otpCooldown))
|
|
165
|
+
: l.sendOtpButton] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "forgot-new-password", className: AUTH_FIELD_LABEL_CLASS, children: l.newPasswordLabel }), _jsx("input", { id: "forgot-new-password", type: "password", placeholder: l.newPasswordPlaceholder, value: newPassword, onChange: (e) => setNewPassword(e.target.value), required: true, autoComplete: "new-password", disabled: resetting, className: AUTH_INPUT_CLASS })] }), _jsxs("button", { type: "submit", disabled: resetting || !otpSent, className: AUTH_PRIMARY_BUTTON_CLASS, children: [resetting && _jsx(AuthSpinner, {}), resetting ? l.submittingButton : l.resetButton] }), _jsx("button", { type: "button", onClick: () => switchMode('email'), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.useEmailResetText })] })) : (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [error && _jsx(AuthErrorBanner, { message: error }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "forgot-email", className: AUTH_FIELD_LABEL_CLASS, children: l.emailLabel }), _jsx("input", { id: "forgot-email", type: "email", placeholder: l.emailPlaceholder, value: email, onChange: (e) => setEmail(e.target.value), required: true, autoComplete: "email", disabled: isLoading, className: AUTH_INPUT_CLASS })] }), _jsxs("button", { type: "submit", disabled: isLoading, className: AUTH_PRIMARY_BUTTON_CLASS, children: [isLoading && _jsx(AuthSpinner, {}), isLoading ? l.submittingButton : l.submitButton] }), phoneOtpEnabled && (_jsx("button", { type: "button", onClick: () => switchMode('phone'), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.usePhoneResetText }))] })), loginUrl && (_jsxs("p", { className: "px-8 text-center text-sm text-muted-foreground", children: [l.rememberPasswordText, ' ', _jsx(LinkComp, { href: loginUrl, className: AUTH_LINK_CLASS, children: l.signInText })] }))] }));
|
|
65
166
|
}
|
package/dist/LoginForm.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ import type { AuthLinkComponentProps } from './types';
|
|
|
11
11
|
export interface LoginFormLabels {
|
|
12
12
|
emailLabel?: string;
|
|
13
13
|
emailPlaceholder?: string;
|
|
14
|
+
/** Identifier label when phone+password is enabled (defaults to "Email or phone number"). */
|
|
15
|
+
emailOrPhoneLabel?: string;
|
|
16
|
+
/** Identifier placeholder when phone+password is enabled. */
|
|
17
|
+
emailOrPhonePlaceholder?: string;
|
|
14
18
|
passwordLabel?: string;
|
|
15
19
|
passwordPlaceholder?: string;
|
|
16
20
|
forgotPasswordText?: string;
|
|
@@ -30,6 +34,22 @@ export interface LoginFormLabels {
|
|
|
30
34
|
usePasswordText?: string;
|
|
31
35
|
/** Link to collapse the revealed break-glass form back to SSO-only (defaults to "Back to single sign-on"). */
|
|
32
36
|
backToSsoText?: string;
|
|
37
|
+
/** Phone-OTP mode (framework#2780) — phone field label (defaults to "Phone number"). */
|
|
38
|
+
phoneLabel?: string;
|
|
39
|
+
/** Phone-OTP mode — phone field placeholder (defaults to "+1 555 000 0000"). */
|
|
40
|
+
phonePlaceholder?: string;
|
|
41
|
+
/** Phone-OTP mode — code field label (defaults to "Verification code"). */
|
|
42
|
+
otpCodeLabel?: string;
|
|
43
|
+
/** Phone-OTP mode — code field placeholder (defaults to "6-digit code"). */
|
|
44
|
+
otpCodePlaceholder?: string;
|
|
45
|
+
/** Phone-OTP mode — "send code" button (defaults to "Get code"). */
|
|
46
|
+
sendOtpButton?: string;
|
|
47
|
+
/** Phone-OTP mode — resend countdown, `{seconds}` hole (defaults to "Resend in {seconds}s"). */
|
|
48
|
+
resendOtpCountdownText?: string;
|
|
49
|
+
/** Link that switches to phone-OTP sign-in (defaults to "Sign in with verification code"). */
|
|
50
|
+
usePhoneOtpText?: string;
|
|
51
|
+
/** Link that switches back to password sign-in (defaults to "Sign in with password instead"). */
|
|
52
|
+
usePasswordSignInText?: string;
|
|
33
53
|
/** Description shown above the form in SSO-only mode (defaults to "Sign in with your organization's single sign-on"). */
|
|
34
54
|
ssoOnlyDescription?: string;
|
|
35
55
|
}
|
package/dist/LoginForm.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginForm.d.ts","sourceRoot":"","sources":["../src/LoginForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"LoginForm.d.ts","sourceRoot":"","sources":["../src/LoginForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAYtD,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8GAA8G;IAC9G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,8FAA8F;IAC9F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iGAAiG;IACjG,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,yHAAyH;IACzH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC5D,uCAAuC;IACvC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,OAAO,EACP,WAAyB,EACzB,iBAAsC,EACtC,KAAiC,EACjC,WAAyD,EACzD,IAAI,EACJ,QAAgB,EAChB,aAAa,EAAE,QAAsB,EACrC,MAAW,EACX,aAAa,GACd,EAAE,cAAc,qBAsZhB"}
|
package/dist/LoginForm.js
CHANGED
|
@@ -9,6 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { useEffect, useState } from 'react';
|
|
10
10
|
import { useAuth } from './useAuth';
|
|
11
11
|
import { SocialSignInButtons } from './SocialSignInButtons';
|
|
12
|
+
import { looksLikePhoneIdentifier, normalizePhoneIdentifier } from './phone-identifier';
|
|
12
13
|
import { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthDivider, AuthErrorBanner, AuthFormHeader, AuthSpinner, } from './authStyles';
|
|
13
14
|
const DefaultLink = ({ href, className, children }) => (_jsx("a", { href: href, className: className, children: children }));
|
|
14
15
|
const DefaultLockIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className: "h-6 w-6", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }));
|
|
@@ -27,10 +28,28 @@ const DefaultLockIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none"
|
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
30
|
export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgotPasswordUrl = '/forgot-password', title = 'Sign in to your account', description = 'Enter your email and password to continue', icon, hideIcon = false, linkComponent: LinkComp = DefaultLink, labels = {}, errorMessages, }) {
|
|
30
|
-
const { signIn, isLoading, getAuthConfig } = useAuth();
|
|
31
|
+
const { signIn, isLoading, getAuthConfig, sendPhoneOtp, signInWithPhoneOtp, signInWithPhonePassword } = useAuth();
|
|
31
32
|
const [email, setEmail] = useState('');
|
|
32
33
|
const [password, setPassword] = useState('');
|
|
33
34
|
const [error, setError] = useState(null);
|
|
35
|
+
// Phone-OTP sign-in (framework#2780). Only offered when the server reports
|
|
36
|
+
// `features.phoneNumberOtp` — i.e. the phoneNumber plugin is on AND a
|
|
37
|
+
// deliverable SMS service is wired — so the mode never renders as a dead
|
|
38
|
+
// entry point whose code can never arrive.
|
|
39
|
+
const [phoneOtpEnabled, setPhoneOtpEnabled] = useState(false);
|
|
40
|
+
// Phone + password sign-in (framework#2780). Offered when the server reports
|
|
41
|
+
// `features.phoneNumber` — the phoneNumber plugin is on. Unlike OTP this needs
|
|
42
|
+
// no SMS service, so it's gated on the coarser flag: the password-mode
|
|
43
|
+
// identifier field then accepts an email OR a phone number.
|
|
44
|
+
const [phonePasswordEnabled, setPhonePasswordEnabled] = useState(false);
|
|
45
|
+
const [mode, setMode] = useState('password');
|
|
46
|
+
const [phone, setPhone] = useState('');
|
|
47
|
+
const [otpCode, setOtpCode] = useState('');
|
|
48
|
+
const [otpSending, setOtpSending] = useState(false);
|
|
49
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
50
|
+
// Seconds until another code may be requested — mirrors the server's
|
|
51
|
+
// per-number cooldown so the button doesn't invite guaranteed 429s.
|
|
52
|
+
const [otpCooldown, setOtpCooldown] = useState(0);
|
|
34
53
|
const [hasSocialProviders, setHasSocialProviders] = useState(false);
|
|
35
54
|
// Enterprise SSO is opt-in server-side (`@better-auth/sso`). Mirror the
|
|
36
55
|
// social-provider pattern below: ask the server whether SSO is wired and
|
|
@@ -47,6 +66,10 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
47
66
|
// Break-glass: reveal the password form for the env owner / local admin even
|
|
48
67
|
// under enforced mode (e.g. during an IdP outage).
|
|
49
68
|
const [showPasswordFallback, setShowPasswordFallback] = useState(false);
|
|
69
|
+
// In-flight `/sign-in/sso` round-trip — the button needs its own pending
|
|
70
|
+
// state because SSO routing is a raw fetch, not the shared `signIn` (whose
|
|
71
|
+
// `isLoading` drives the password submit button).
|
|
72
|
+
const [ssoSubmitting, setSsoSubmitting] = useState(false);
|
|
50
73
|
useEffect(() => {
|
|
51
74
|
let cancelled = false;
|
|
52
75
|
Promise.resolve()
|
|
@@ -55,6 +78,8 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
55
78
|
if (cancelled)
|
|
56
79
|
return;
|
|
57
80
|
setSsoEnabled(config?.features?.sso === true);
|
|
81
|
+
setPhoneOtpEnabled(config?.features?.phoneNumberOtp === true);
|
|
82
|
+
setPhonePasswordEnabled(config?.features?.phoneNumber === true);
|
|
58
83
|
setSsoEnforced(config?.features?.ssoEnforced === true ||
|
|
59
84
|
config?.emailPassword?.enabled === false);
|
|
60
85
|
})
|
|
@@ -72,6 +97,8 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
72
97
|
const l = {
|
|
73
98
|
emailLabel: labels.emailLabel ?? 'Email',
|
|
74
99
|
emailPlaceholder: labels.emailPlaceholder ?? 'name@example.com',
|
|
100
|
+
emailOrPhoneLabel: labels.emailOrPhoneLabel ?? 'Email or phone number',
|
|
101
|
+
emailOrPhonePlaceholder: labels.emailOrPhonePlaceholder ?? 'name@example.com or +1 555 000 0000',
|
|
75
102
|
passwordLabel: labels.passwordLabel ?? 'Password',
|
|
76
103
|
passwordPlaceholder: labels.passwordPlaceholder ?? 'Enter your password',
|
|
77
104
|
forgotPasswordText: labels.forgotPasswordText ?? 'Forgot password?',
|
|
@@ -84,12 +111,38 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
84
111
|
usePasswordText: labels.usePasswordText ?? 'Use a password instead',
|
|
85
112
|
backToSsoText: labels.backToSsoText ?? 'Back to single sign-on',
|
|
86
113
|
ssoOnlyDescription: labels.ssoOnlyDescription ?? "Sign in with your organization's single sign-on",
|
|
114
|
+
phoneLabel: labels.phoneLabel ?? 'Phone number',
|
|
115
|
+
phonePlaceholder: labels.phonePlaceholder ?? '+1 555 000 0000',
|
|
116
|
+
otpCodeLabel: labels.otpCodeLabel ?? 'Verification code',
|
|
117
|
+
otpCodePlaceholder: labels.otpCodePlaceholder ?? '6-digit code',
|
|
118
|
+
sendOtpButton: labels.sendOtpButton ?? 'Get code',
|
|
119
|
+
resendOtpCountdownText: labels.resendOtpCountdownText ?? 'Resend in {seconds}s',
|
|
120
|
+
usePhoneOtpText: labels.usePhoneOtpText ?? 'Sign in with verification code',
|
|
121
|
+
usePasswordSignInText: labels.usePasswordSignInText ?? 'Sign in with password instead',
|
|
87
122
|
};
|
|
123
|
+
// Tick the resend cooldown down once per second while it's armed.
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (otpCooldown <= 0)
|
|
126
|
+
return;
|
|
127
|
+
const timer = setTimeout(() => setOtpCooldown((s) => (s > 1 ? s - 1 : 0)), 1000);
|
|
128
|
+
return () => clearTimeout(timer);
|
|
129
|
+
}, [otpCooldown]);
|
|
88
130
|
const handleSubmit = async (e) => {
|
|
89
131
|
e.preventDefault();
|
|
90
132
|
setError(null);
|
|
91
133
|
try {
|
|
92
|
-
|
|
134
|
+
if (mode === 'phone-otp') {
|
|
135
|
+
await signInWithPhoneOtp(phone.trim(), otpCode.trim());
|
|
136
|
+
}
|
|
137
|
+
else if (phonePasswordEnabled && looksLikePhoneIdentifier(email)) {
|
|
138
|
+
// Unified identifier: a phone-shaped entry routes to phone+password.
|
|
139
|
+
// Normalize identically to the backend (strip formatting, no country
|
|
140
|
+
// code) or the phoneNumber lookup fails.
|
|
141
|
+
await signInWithPhonePassword(normalizePhoneIdentifier(email) ?? email.trim(), password);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
await signIn(email, password);
|
|
145
|
+
}
|
|
93
146
|
onSuccess?.();
|
|
94
147
|
}
|
|
95
148
|
catch (err) {
|
|
@@ -99,6 +152,35 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
99
152
|
onError?.(authError);
|
|
100
153
|
}
|
|
101
154
|
};
|
|
155
|
+
/**
|
|
156
|
+
* Request a sign-in OTP for the entered number. A 429 (per-number cooldown
|
|
157
|
+
* / hourly cap, framework#2780) surfaces the server's honest "retry in Ns"
|
|
158
|
+
* message; any other failure surfaces as-is.
|
|
159
|
+
*/
|
|
160
|
+
const handleSendOtp = async () => {
|
|
161
|
+
if (!phone.trim() || otpSending || otpCooldown > 0)
|
|
162
|
+
return;
|
|
163
|
+
setError(null);
|
|
164
|
+
setOtpSending(true);
|
|
165
|
+
try {
|
|
166
|
+
await sendPhoneOtp(phone.trim());
|
|
167
|
+
setOtpSent(true);
|
|
168
|
+
setOtpCooldown(60);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
const authError = err instanceof Error ? err : new Error(String(err));
|
|
172
|
+
const code = authError.code;
|
|
173
|
+
setError((code && errorMessages?.[code]) || authError.message);
|
|
174
|
+
onError?.(authError);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
setOtpSending(false);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const switchMode = (next) => {
|
|
181
|
+
setMode(next);
|
|
182
|
+
setError(null);
|
|
183
|
+
};
|
|
102
184
|
/**
|
|
103
185
|
* Federated SSO sign-in: routes the entered email to its configured IdP
|
|
104
186
|
* (better-auth `@better-auth/sso`, by email domain) and redirects the
|
|
@@ -106,7 +188,15 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
106
188
|
* error when no provider matches the domain.
|
|
107
189
|
*/
|
|
108
190
|
const handleSso = async () => {
|
|
191
|
+
if (ssoSubmitting)
|
|
192
|
+
return;
|
|
109
193
|
setError(null);
|
|
194
|
+
// SSO routes by email domain — a phone-shaped identifier can't map to an IdP.
|
|
195
|
+
if (looksLikePhoneIdentifier(email)) {
|
|
196
|
+
setError('Enter your email address to sign in with SSO.');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
setSsoSubmitting(true);
|
|
110
200
|
try {
|
|
111
201
|
const base = window.location.pathname.replace(/\/login(?:\/.*)?$/, '');
|
|
112
202
|
const res = await fetch('/api/v1/auth/sign-in/sso', {
|
|
@@ -117,14 +207,22 @@ export function LoginForm({ onSuccess, onError, registerUrl = '/register', forgo
|
|
|
117
207
|
});
|
|
118
208
|
const data = (await res.json().catch(() => ({})));
|
|
119
209
|
if (res.ok && typeof data.url === 'string') {
|
|
210
|
+
// Navigating to the IdP — keep the pending state through teardown.
|
|
120
211
|
window.location.href = data.url;
|
|
121
212
|
return;
|
|
122
213
|
}
|
|
123
214
|
setError(data.message || 'No SSO provider is configured for this email domain.');
|
|
215
|
+
setSsoSubmitting(false);
|
|
124
216
|
}
|
|
125
217
|
catch (err) {
|
|
126
218
|
setError(err instanceof Error ? err.message : String(err));
|
|
219
|
+
setSsoSubmitting(false);
|
|
127
220
|
}
|
|
128
221
|
};
|
|
129
|
-
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: hideIcon ? undefined : (icon ?? _jsx(DefaultLockIcon, {})), title: title, description: ssoEnforced && !showPasswordFallback ? l.ssoOnlyDescription : description }), _jsxs("div", { className: "space-y-5", children: [_jsx(SocialSignInButtons, { mode: "sign-in", onProvidersResolved: (hasProviders) => setHasSocialProviders(hasProviders) }), passwordFormVisible ? (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [hasSocialProviders && _jsx(AuthDivider, { label: l.orText }), error && _jsx(AuthErrorBanner, { message: error }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "login-
|
|
222
|
+
return (_jsxs("div", { className: "mx-auto flex w-full flex-col justify-center space-y-7 sm:w-[400px]", children: [_jsx(AuthFormHeader, { icon: hideIcon ? undefined : (icon ?? _jsx(DefaultLockIcon, {})), title: title, description: ssoEnforced && !showPasswordFallback ? l.ssoOnlyDescription : description }), _jsxs("div", { className: "space-y-5", children: [_jsx(SocialSignInButtons, { mode: "sign-in", onProvidersResolved: (hasProviders) => setHasSocialProviders(hasProviders) }), passwordFormVisible ? (mode === 'phone-otp' ? (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [hasSocialProviders && _jsx(AuthDivider, { label: l.orText }), error && _jsx(AuthErrorBanner, { message: error }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "login-phone", className: AUTH_FIELD_LABEL_CLASS, children: l.phoneLabel }), _jsx("input", { id: "login-phone", type: "tel", placeholder: l.phonePlaceholder, value: phone, onChange: (e) => setPhone(e.target.value), required: true, autoComplete: "tel", disabled: isLoading, className: AUTH_INPUT_CLASS })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "login-otp", className: AUTH_FIELD_LABEL_CLASS, children: l.otpCodeLabel }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { id: "login-otp", type: "text", inputMode: "numeric", placeholder: l.otpCodePlaceholder, value: otpCode, onChange: (e) => setOtpCode(e.target.value), required: true, autoComplete: "one-time-code", disabled: isLoading, className: AUTH_INPUT_CLASS }), _jsxs("button", { type: "button", onClick: handleSendOtp, disabled: otpSending || otpCooldown > 0 || !phone.trim() || isLoading, className: "shrink-0 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium transition-colors hover:bg-accent disabled:opacity-50", children: [otpSending && _jsx(AuthSpinner, {}), otpCooldown > 0
|
|
223
|
+
? l.resendOtpCountdownText.replace('{seconds}', String(otpCooldown))
|
|
224
|
+
: l.sendOtpButton] })] })] }), _jsxs("button", { type: "submit", disabled: isLoading || !otpSent, className: AUTH_PRIMARY_BUTTON_CLASS, children: [isLoading && _jsx(AuthSpinner, {}), isLoading ? l.submittingButton : l.submitButton] }), _jsx("button", { type: "button", onClick: () => switchMode('password'), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.usePasswordSignInText })] })) : (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [hasSocialProviders && _jsx(AuthDivider, { label: l.orText }), error && _jsx(AuthErrorBanner, { message: error }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "login-email", className: AUTH_FIELD_LABEL_CLASS, children: phonePasswordEnabled ? l.emailOrPhoneLabel : l.emailLabel }), _jsx("input", { id: "login-email",
|
|
225
|
+
// `text` (not `email`) when a phone number is also accepted, so the
|
|
226
|
+
// browser doesn't reject a phone-shaped value on native validation.
|
|
227
|
+
type: phonePasswordEnabled ? 'text' : 'email', inputMode: phonePasswordEnabled ? 'text' : 'email', placeholder: phonePasswordEnabled ? l.emailOrPhonePlaceholder : l.emailPlaceholder, value: email, onChange: (e) => setEmail(e.target.value), required: true, autoComplete: "username", disabled: isLoading, className: AUTH_INPUT_CLASS })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { htmlFor: "login-password", className: AUTH_FIELD_LABEL_CLASS, children: l.passwordLabel }), forgotPasswordUrl && (_jsx(LinkComp, { href: forgotPasswordUrl, className: "text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.forgotPasswordText }))] }), _jsx("input", { id: "login-password", type: "password", placeholder: l.passwordPlaceholder, value: password, onChange: (e) => setPassword(e.target.value), required: true, autoComplete: "current-password", disabled: isLoading, className: AUTH_INPUT_CLASS })] }), _jsxs("button", { type: "submit", disabled: isLoading, className: AUTH_PRIMARY_BUTTON_CLASS, children: [isLoading && _jsx(AuthSpinner, {}), isLoading ? l.submittingButton : l.submitButton] }), ssoEnabled && (_jsxs("button", { type: "button", onClick: handleSso, disabled: isLoading || ssoSubmitting, "aria-busy": ssoSubmitting, className: "flex w-full items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent disabled:opacity-50", children: [ssoSubmitting && _jsx(AuthSpinner, {}), l.ssoButton] })), phoneOtpEnabled && (_jsx("button", { type: "button", onClick: () => switchMode('phone-otp'), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.usePhoneOtpText })), ssoEnforced && showPasswordFallback && (_jsx("button", { type: "button", onClick: () => setShowPasswordFallback(false), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.backToSsoText }))] }))) : (_jsxs("div", { className: "space-y-4", children: [error && _jsx(AuthErrorBanner, { message: error }), _jsx("button", { type: "button", onClick: () => setShowPasswordFallback(true), className: "w-full text-center text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-primary hover:underline", children: l.usePasswordText })] }))] }), registerUrl && !ssoEnforced && (_jsxs("p", { className: "px-8 text-center text-sm text-muted-foreground", children: [l.noAccountText, ' ', _jsx(LinkComp, { href: registerUrl, className: AUTH_LINK_CLASS, children: l.signUpText })] }))] }));
|
|
130
228
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SocialSignInButtons.d.ts","sourceRoot":"","sources":["../src/SocialSignInButtons.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SocialSignInButtons.d.ts","sourceRoot":"","sources":["../src/SocialSignInButtons.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAkFnD,MAAM,WAAW,wBAAwB;IACvC,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,mBAAmB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,WAAsC,EACtC,mBAAmB,GACpB,EAAE,wBAAwB,4BAyF1B"}
|
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { useEffect, useState } from 'react';
|
|
10
10
|
import { useAuth } from './useAuth';
|
|
11
|
+
import { AuthSpinner } from './authStyles';
|
|
11
12
|
// Brand name overrides for providers whose `name` from the server may be unset
|
|
12
13
|
// or where we want to canonicalize casing.
|
|
13
14
|
const PROVIDER_LABEL = {
|
|
@@ -52,6 +53,10 @@ export function SocialSignInButtons({ mode = 'sign-in', callbackURL, errorCallba
|
|
|
52
53
|
const [providers, setProviders] = useState([]);
|
|
53
54
|
const [loading, setLoading] = useState(true);
|
|
54
55
|
const [error, setError] = useState(null);
|
|
56
|
+
// Provider id of the in-flight `signInWithProvider` call. Sign-in ends in a
|
|
57
|
+
// full-page navigation, so the spinner deliberately stays on until unload —
|
|
58
|
+
// it is only cleared when the call FAILS and the user needs the button back.
|
|
59
|
+
const [pendingId, setPendingId] = useState(null);
|
|
55
60
|
useEffect(() => {
|
|
56
61
|
let cancelled = false;
|
|
57
62
|
// Wrap in Promise.resolve so synchronous throws (e.g. mock client without
|
|
@@ -84,17 +89,23 @@ export function SocialSignInButtons({ mode = 'sign-in', callbackURL, errorCallba
|
|
|
84
89
|
const label = mode === 'sign-in' ? 'Continue with' : 'Sign up with';
|
|
85
90
|
const defaultCallback = typeof window !== 'undefined' ? window.location.href : undefined;
|
|
86
91
|
const onClick = async (provider) => {
|
|
92
|
+
if (pendingId)
|
|
93
|
+
return;
|
|
87
94
|
setError(null);
|
|
95
|
+
setPendingId(provider.id);
|
|
88
96
|
try {
|
|
89
97
|
await signInWithProvider(provider.id, {
|
|
90
98
|
callbackURL: callbackURL ?? defaultCallback,
|
|
91
99
|
errorCallbackURL: errorCallbackURL ?? callbackURL ?? defaultCallback,
|
|
92
100
|
type: provider.type ?? 'social',
|
|
93
101
|
});
|
|
102
|
+
// Success = the browser is navigating to the provider. Keep the pending
|
|
103
|
+
// state so the button doesn't "un-busy" during the page teardown.
|
|
94
104
|
}
|
|
95
105
|
catch (err) {
|
|
96
106
|
setError(err instanceof Error ? err.message : String(err));
|
|
107
|
+
setPendingId(null);
|
|
97
108
|
}
|
|
98
109
|
};
|
|
99
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [error && (_jsx("div", { className: "rounded-md bg-destructive/15 p-3 text-sm text-destructive", role: "alert", children: error })), providers.map((p) => (_jsxs("button", { type: "button", onClick: () => onClick(p), className: "inline-flex h-10 w-full items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", children: [_jsx(ProviderIcon, { id: p.id }), label, " ", PROVIDER_LABEL[p.id] ?? p.name] }, p.id))), _jsxs("div", { className: "relative my-1", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("span", { className: "w-full border-t" }) }), _jsx("div", { className: "relative flex justify-center text-xs uppercase", children: _jsx("span", { className: "bg-background px-2 text-muted-foreground", children: dividerText }) })] })] }));
|
|
110
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [error && (_jsx("div", { className: "rounded-md bg-destructive/15 p-3 text-sm text-destructive", role: "alert", children: error })), providers.map((p) => (_jsxs("button", { type: "button", onClick: () => onClick(p), disabled: pendingId !== null, "aria-busy": pendingId === p.id, className: "inline-flex h-10 w-full items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", children: [pendingId === p.id ? _jsx(AuthSpinner, {}) : _jsx(ProviderIcon, { id: p.id }), label, " ", PROVIDER_LABEL[p.id] ?? p.name] }, p.id))), _jsxs("div", { className: "relative my-1", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("span", { className: "w-full border-t" }) }), _jsx("div", { className: "relative flex justify-center text-xs uppercase", children: _jsx("span", { className: "bg-background px-2 text-muted-foreground", children: dividerText }) })] })] }));
|
|
100
111
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createAuthClient.d.ts","sourceRoot":"","sources":["../src/createAuthClient.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EACV,UAAU,EAAE,gBAAgB,EAE7B,MAAM,SAAS,CAAC;AAIjB;;;GAGG;AACH,eAAO,MAAM,YAAY;kBACD,MAAM,GAAG,IAAI;WAE5B,MAAM,GAAG,IAAI;eAST,MAAM,GAAG,IAAI;aASf,IAAI;CAQd,CAAC;AAmHF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"createAuthClient.d.ts","sourceRoot":"","sources":["../src/createAuthClient.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EACV,UAAU,EAAE,gBAAgB,EAE7B,MAAM,SAAS,CAAC;AAIjB;;;GAGG;AACH,eAAO,MAAM,YAAY;kBACD,MAAM,GAAG,IAAI;WAE5B,MAAM,GAAG,IAAI;eAST,MAAM,GAAG,IAAI;aASf,IAAI;CAQd,CAAC;AAmHF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAkkBrE"}
|
package/dist/createAuthClient.js
CHANGED
|
@@ -173,6 +173,29 @@ export function createAuthClient(config) {
|
|
|
173
173
|
// than the runtime JSON the server actually sends (e.g. `session` on signIn).
|
|
174
174
|
// We deliberately cast through `unknown` to bridge from better-auth types
|
|
175
175
|
// to the ObjectUI AuthClient contract.
|
|
176
|
+
/**
|
|
177
|
+
* POST one of the phoneNumber plugin's endpoints (framework#2780) and
|
|
178
|
+
* return the parsed JSON. Throws an Error carrying the better-auth `code`
|
|
179
|
+
* and HTTP `status` so the forms can special-case the per-number cooldown
|
|
180
|
+
* (429 TOO_MANY_REQUESTS) with an honest "retry in Ns" message.
|
|
181
|
+
*/
|
|
182
|
+
async function postPhoneNumberEndpoint(path, body) {
|
|
183
|
+
const response = await bearerFetch(`${origin}${basePath}${path}`, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: { 'Content-Type': 'application/json' },
|
|
186
|
+
credentials: 'include',
|
|
187
|
+
body: JSON.stringify(body),
|
|
188
|
+
});
|
|
189
|
+
const payload = (await response.json().catch(() => ({})));
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
const err = new Error(payload?.message ?? `Auth request failed with status ${response.status}`);
|
|
192
|
+
if (payload?.code)
|
|
193
|
+
err.code = payload.code;
|
|
194
|
+
err.status = response.status;
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
return payload ?? {};
|
|
198
|
+
}
|
|
176
199
|
return {
|
|
177
200
|
async signIn(credentials) {
|
|
178
201
|
const { data, error } = await betterAuth.signIn.email({
|
|
@@ -313,6 +336,49 @@ export function createAuthClient(config) {
|
|
|
313
336
|
throw new Error(error.message ?? `Auth request failed with status ${error.status}`);
|
|
314
337
|
}
|
|
315
338
|
},
|
|
339
|
+
// --- Phone-number OTP (framework#2780) -------------------------------
|
|
340
|
+
// The better-auth phoneNumber plugin's endpoints, called directly (the
|
|
341
|
+
// phoneNumberClient plugin adds nothing we need beyond these four POSTs,
|
|
342
|
+
// and direct fetch keeps the error `code`/status handy for the cooldown
|
|
343
|
+
// 429 UX). Server availability is gated by `features.phoneNumberOtp`.
|
|
344
|
+
async sendPhoneOtp(phoneNumber) {
|
|
345
|
+
await postPhoneNumberEndpoint('/phone-number/send-otp', { phoneNumber });
|
|
346
|
+
},
|
|
347
|
+
async signInWithPhoneOtp(phoneNumber, code) {
|
|
348
|
+
const payload = await postPhoneNumberEndpoint('/phone-number/verify', { phoneNumber, code });
|
|
349
|
+
// Response: `{ status, token: string|null, user: object|null }` —
|
|
350
|
+
// token/user are null when the number belongs to no account (the
|
|
351
|
+
// endpoint also serves the change-phone verification flow).
|
|
352
|
+
const token = typeof payload.token === 'string' ? payload.token : undefined;
|
|
353
|
+
const user = (payload.user ?? null);
|
|
354
|
+
if (!token || !user) {
|
|
355
|
+
throw new Error('No account is registered for this phone number.');
|
|
356
|
+
}
|
|
357
|
+
TokenStorage.set(token);
|
|
358
|
+
return { user, session: { token } };
|
|
359
|
+
},
|
|
360
|
+
async signInWithPhonePassword(phoneNumber, password) {
|
|
361
|
+
// Phone + password sign-in (better-auth `/sign-in/phone-number`). Needs
|
|
362
|
+
// no SMS service — gated server-side by `features.phoneNumber` (plugin
|
|
363
|
+
// on), NOT `features.phoneNumberOtp`. Response: `{ token, user }`, mirror
|
|
364
|
+
// of signInWithPhoneOtp.
|
|
365
|
+
const payload = await postPhoneNumberEndpoint('/sign-in/phone-number', { phoneNumber, password });
|
|
366
|
+
const token = typeof payload.token === 'string' ? payload.token : undefined;
|
|
367
|
+
const user = (payload.user ?? null);
|
|
368
|
+
if (!token || !user) {
|
|
369
|
+
throw new Error('Invalid phone number or password.');
|
|
370
|
+
}
|
|
371
|
+
TokenStorage.set(token);
|
|
372
|
+
return { user, session: { token } };
|
|
373
|
+
},
|
|
374
|
+
async requestPhonePasswordReset(phoneNumber) {
|
|
375
|
+
// Always answers `{status:true}` (no account-existence oracle) — the
|
|
376
|
+
// OTP only arrives when the number belongs to an account.
|
|
377
|
+
await postPhoneNumberEndpoint('/phone-number/request-password-reset', { phoneNumber });
|
|
378
|
+
},
|
|
379
|
+
async resetPasswordWithPhoneOtp(phoneNumber, otp, newPassword) {
|
|
380
|
+
await postPhoneNumberEndpoint('/phone-number/reset-password', { phoneNumber, otp, newPassword });
|
|
381
|
+
},
|
|
316
382
|
async changePassword(currentPassword, newPassword, options) {
|
|
317
383
|
const fn = betterAuth.changePassword;
|
|
318
384
|
if (typeof fn !== 'function') {
|
|
@@ -436,7 +502,12 @@ export function createAuthClient(config) {
|
|
|
436
502
|
if (error) {
|
|
437
503
|
throw new Error(error.message ?? `Auth request failed with status ${error.status}`);
|
|
438
504
|
}
|
|
439
|
-
|
|
505
|
+
// A missing redirect URL means the flow CANNOT continue — surface it
|
|
506
|
+
// instead of resolving silently (the user would see a dead button).
|
|
507
|
+
if (!data?.url) {
|
|
508
|
+
throw new Error(`Sign-in with "${providerId}" did not return a redirect URL — please try again or contact your administrator.`);
|
|
509
|
+
}
|
|
510
|
+
if (typeof window !== 'undefined') {
|
|
440
511
|
window.location.href = data.url;
|
|
441
512
|
}
|
|
442
513
|
return;
|
|
@@ -450,7 +521,10 @@ export function createAuthClient(config) {
|
|
|
450
521
|
throw new Error(error.message ?? `Auth request failed with status ${error.status}`);
|
|
451
522
|
}
|
|
452
523
|
const socialUrl = data?.url;
|
|
453
|
-
if (socialUrl
|
|
524
|
+
if (!socialUrl) {
|
|
525
|
+
throw new Error(`Sign-in with "${providerId}" did not return a redirect URL — please try again or contact your administrator.`);
|
|
526
|
+
}
|
|
527
|
+
if (typeof window !== 'undefined') {
|
|
454
528
|
window.location.href = socialUrl;
|
|
455
529
|
}
|
|
456
530
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export { SocialSignInButtons, type SocialSignInButtonsProps } from './SocialSign
|
|
|
31
31
|
export { UserMenu, type UserMenuProps } from './UserMenu';
|
|
32
32
|
export { PreviewBanner, type PreviewBannerProps } from './PreviewBanner';
|
|
33
33
|
export { createAuthClient, TokenStorage } from './createAuthClient';
|
|
34
|
+
export { normalizePhoneIdentifier, looksLikePhoneIdentifier } from './phone-identifier';
|
|
34
35
|
export { createAuthenticatedFetch, ActiveOrganizationStorage, type AuthenticatedAdapterOptions } from './createAuthenticatedFetch';
|
|
35
36
|
export { getUserInitials } from './types';
|
|
36
37
|
export { AUTH_FIELD_LABEL_CLASS, AUTH_INPUT_CLASS, AUTH_LINK_CLASS, AUTH_PRIMARY_BUTTON_CLASS, AuthAlertIcon, AuthCheckIcon, AuthDivider, AuthErrorBanner, AuthFormHeader, AuthMailIcon, AuthSpinner, } from './authStyles';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACvH,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,KAAK,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACnI,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACvH,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,KAAK,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACnI,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,EACZ,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { SocialSignInButtons } from './SocialSignInButtons';
|
|
|
31
31
|
export { UserMenu } from './UserMenu';
|
|
32
32
|
export { PreviewBanner } from './PreviewBanner';
|
|
33
33
|
export { createAuthClient, TokenStorage } from './createAuthClient';
|
|
34
|
+
export { normalizePhoneIdentifier, looksLikePhoneIdentifier } from './phone-identifier';
|
|
34
35
|
export { createAuthenticatedFetch, ActiveOrganizationStorage } from './createAuthenticatedFetch';
|
|
35
36
|
export { getUserInitials } from './types';
|
|
36
37
|
// Shared auth form primitives — exposed so consumers can build custom forms
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier helpers shared by the login surfaces for phone sign-in
|
|
3
|
+
* (framework#2780).
|
|
4
|
+
*
|
|
5
|
+
* These MIRROR the backend's `normalizePhoneNumber`
|
|
6
|
+
* (`@objectstack/plugin-auth`): strip spaces / dashes / parens / dots, then
|
|
7
|
+
* validate `^\+?[0-9]{6,15}$`. They deliberately do NOT force E.164 or inject a
|
|
8
|
+
* country code — accounts are stored in exactly this light-stripped form, so
|
|
9
|
+
* anything heavier would fail the `phoneNumber` lookup at sign-in. Keep this in
|
|
10
|
+
* lock-step with the backend regex.
|
|
11
|
+
*/
|
|
12
|
+
/** Strip formatting and validate; returns the canonical string or `null`. */
|
|
13
|
+
export declare function normalizePhoneIdentifier(raw: string): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Whether an identifier should be treated as a phone number rather than an
|
|
16
|
+
* email. An `@` always means email; otherwise it is a phone only when it
|
|
17
|
+
* normalizes to a valid number.
|
|
18
|
+
*/
|
|
19
|
+
export declare function looksLikePhoneIdentifier(raw: string): boolean;
|
|
20
|
+
//# sourceMappingURL=phone-identifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-identifier.d.ts","sourceRoot":"","sources":["../src/phone-identifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,6EAA6E;AAC7E,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG7D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier helpers shared by the login surfaces for phone sign-in
|
|
3
|
+
* (framework#2780).
|
|
4
|
+
*
|
|
5
|
+
* These MIRROR the backend's `normalizePhoneNumber`
|
|
6
|
+
* (`@objectstack/plugin-auth`): strip spaces / dashes / parens / dots, then
|
|
7
|
+
* validate `^\+?[0-9]{6,15}$`. They deliberately do NOT force E.164 or inject a
|
|
8
|
+
* country code — accounts are stored in exactly this light-stripped form, so
|
|
9
|
+
* anything heavier would fail the `phoneNumber` lookup at sign-in. Keep this in
|
|
10
|
+
* lock-step with the backend regex.
|
|
11
|
+
*/
|
|
12
|
+
/** Strip formatting and validate; returns the canonical string or `null`. */
|
|
13
|
+
export function normalizePhoneIdentifier(raw) {
|
|
14
|
+
const stripped = String(raw ?? '').replace(/[\s\-().]/g, '');
|
|
15
|
+
return /^\+?[0-9]{6,15}$/.test(stripped) ? stripped : null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether an identifier should be treated as a phone number rather than an
|
|
19
|
+
* email. An `@` always means email; otherwise it is a phone only when it
|
|
20
|
+
* normalizes to a valid number.
|
|
21
|
+
*/
|
|
22
|
+
export function looksLikePhoneIdentifier(raw) {
|
|
23
|
+
if (typeof raw !== 'string' || raw.includes('@'))
|
|
24
|
+
return false;
|
|
25
|
+
return normalizePhoneIdentifier(raw) !== null;
|
|
26
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -121,6 +121,26 @@ export interface AuthPublicConfig {
|
|
|
121
121
|
* invite-accept) still work — only fresh creation is forbidden.
|
|
122
122
|
*/
|
|
123
123
|
multiOrgEnabled?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Phone-number sign-in enabled server-side (better-auth phoneNumber
|
|
126
|
+
* plugin — phone + password, framework#2766).
|
|
127
|
+
*/
|
|
128
|
+
phoneNumber?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Phone-number OTP sign-in / self-service reset available — requires the
|
|
131
|
+
* phoneNumber plugin plus a deliverable SMS service (framework#2780).
|
|
132
|
+
* Gates the "sign in with verification code" mode and the phone branch
|
|
133
|
+
* of the forgot-password form; falsy hides them so we never render an
|
|
134
|
+
* entry point whose code can never arrive.
|
|
135
|
+
*/
|
|
136
|
+
phoneNumberOtp?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* better-auth admin plugin mounted server-side (`auth.plugins.admin`;
|
|
139
|
+
* SCIM forces it on). Gates the admin user-management surfaces — the
|
|
140
|
+
* sys_user create/set-password actions and the identity import wizard
|
|
141
|
+
* entry (framework#2766 / #2782).
|
|
142
|
+
*/
|
|
143
|
+
admin?: boolean;
|
|
124
144
|
};
|
|
125
145
|
}
|
|
126
146
|
/** Options when initiating a third-party sign-in */
|
|
@@ -158,6 +178,22 @@ export interface AuthClient {
|
|
|
158
178
|
sendVerificationEmail: (email: string, callbackURL?: string) => Promise<void>;
|
|
159
179
|
/** Reset password with token */
|
|
160
180
|
resetPassword: (token: string, newPassword: string) => Promise<void>;
|
|
181
|
+
/** framework#2780 — request a sign-in/verification OTP SMS for the phone number. */
|
|
182
|
+
sendPhoneOtp: (phoneNumber: string) => Promise<void>;
|
|
183
|
+
/** framework#2780 — verify a phone OTP; signs in the number's user. */
|
|
184
|
+
signInWithPhoneOtp: (phoneNumber: string, code: string) => Promise<{
|
|
185
|
+
user: AuthUser;
|
|
186
|
+
session: AuthSession;
|
|
187
|
+
}>;
|
|
188
|
+
/** framework#2780 — sign in with phone number + password (no SMS; gated by `features.phoneNumber`). */
|
|
189
|
+
signInWithPhonePassword: (phoneNumber: string, password: string) => Promise<{
|
|
190
|
+
user: AuthUser;
|
|
191
|
+
session: AuthSession;
|
|
192
|
+
}>;
|
|
193
|
+
/** framework#2780 — request a password-reset OTP SMS for the phone number. */
|
|
194
|
+
requestPhonePasswordReset: (phoneNumber: string) => Promise<void>;
|
|
195
|
+
/** framework#2780 — reset the password using a phone OTP. */
|
|
196
|
+
resetPasswordWithPhoneOtp: (phoneNumber: string, otp: string, newPassword: string) => Promise<void>;
|
|
161
197
|
/** Change password (requires current password). For users who already have a local password. */
|
|
162
198
|
changePassword: (currentPassword: string, newPassword: string, options?: {
|
|
163
199
|
revokeOtherSessions?: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B;;GAEG;AAEH,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gCAAgC;IAChC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+BAA+B;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWjG;AAED,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,2BAA2B;AAC3B,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,sBAAsB;IACtB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,wCAAwC;IACxC,eAAe,EAAE,OAAO,CAAC;IACzB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,0BAA0B;AAC1B,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,mBAAmB;AACnB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC1B;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,wBAAwB,CAAC,EAAE,OAAO,CAAC;KACpC,CAAC;IACF,eAAe,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB;;;;;WAKG;QACH,GAAG,CAAC,EAAE,OAAO,CAAC;QACd;;;;;;;;;WASG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB;;;;WAIG;QACH,eAAe,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/useAuth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../src/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAW,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,IAAI,gBAAgB,
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../src/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAW,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,IAAI,gBAAgB,CA2D1C"}
|
package/dist/useAuth.js
CHANGED
|
@@ -40,6 +40,11 @@ export function useAuth() {
|
|
|
40
40
|
forgotPassword: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
41
41
|
sendVerificationEmail: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
42
42
|
resetPassword: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
43
|
+
sendPhoneOtp: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
44
|
+
signInWithPhoneOtp: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
45
|
+
signInWithPhonePassword: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
46
|
+
requestPhonePasswordReset: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
47
|
+
resetPasswordWithPhoneOtp: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
43
48
|
changePassword: async () => { throw new Error('useAuth must be used within an AuthProvider'); },
|
|
44
49
|
remediationRequired: null,
|
|
45
50
|
setRemediationRequired: () => { },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Authentication system for Object UI with AuthProvider, useAuth hook, AuthGuard, and form components.",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"better-auth": "^1.6.23",
|
|
34
|
-
"@object-ui/types": "
|
|
34
|
+
"@object-ui/types": "14.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "19.2.17",
|