@seamless-auth/react 0.12.0 → 0.14.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 +78 -0
- package/README.md +122 -6
- package/dist/AuthProvider.d.ts +33 -5
- package/dist/authRoutePaths.d.ts +21 -0
- package/dist/chunks/shared-BzrBuR0z.js +3 -0
- package/dist/chunks/shared-BzrBuR0z.js.map +1 -0
- package/dist/components/AuthFallbackOptions.d.ts +1 -1
- package/dist/hooks/useAuthClient.d.ts +6 -1
- package/dist/hooks/useAuthorizedFetch.d.ts +6 -0
- package/dist/hooks/useLoginMethods.d.ts +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/routes.d.ts +1 -21
- package/dist/routes.js +3 -0
- package/dist/routes.js.map +1 -0
- package/package.json +18 -65
- package/LICENSE.md +0 -26
- package/dist/client/createSeamlessAuthClient.d.ts +0 -189
- package/dist/client/errors.d.ts +0 -90
- package/dist/client/result.d.ts +0 -36
- package/dist/client/webauthnPrf.d.ts +0 -27
- package/dist/client/webauthnSupport.d.ts +0 -10
- package/dist/fetchWithAuth.d.ts +0 -5
- package/dist/scopedRoles.d.ts +0 -2
- package/dist/session/createAuthSession.d.ts +0 -61
- package/dist/session/storage.d.ts +0 -17
- package/dist/types.d.ts +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# @seamless-auth/react
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 419025e: Support the Next.js App Router.
|
|
8
|
+
- Every module in `@seamless-auth/react` now ships with a `'use client'` directive, so `AuthProvider` renders straight from a server layout. The build fails if the directive goes missing from any emitted file.
|
|
9
|
+
- **Breaking:** `AuthRoutes` moved to `@seamless-auth/react/routes`, and `react-router-dom` is now an optional peer. Change `import { AuthRoutes } from '@seamless-auth/react'` to `import { AuthRoutes } from '@seamless-auth/react/routes'`. Applications that do not render the bundled screens no longer need react-router installed.
|
|
10
|
+
- Fixed a hydration mismatch for returning users. `hasSignedInBefore` was read from `localStorage` on the client's first render, which the server could not match. The store now exposes `getServerState()`, and the provider hydrates from it.
|
|
11
|
+
- `AuthProvider` and `createAuthSession` accept `initialSession`, a session the server already resolved (or `null`). The first paint renders it settled instead of loading, and the session then revalidates in the background.
|
|
12
|
+
- `refreshSession({ background: true })` revalidates without reporting `loading`.
|
|
13
|
+
|
|
14
|
+
- 0dfe622: Support signing in through a legacy identity provider during a migration cutover (fells-code/seamless-auth-api#337).
|
|
15
|
+
- `getOAuthErrorCode` recognises `oauth_provider_retired` (the user's organization no longer signs in with that provider) and `oauth_invalid_id_token`.
|
|
16
|
+
- The bundled OAuth callback sends the user to passkey enrollment when the sign-in response carries `nextStep: 'enroll_passkey'`, and the passkey screen then continues to the `returnTo` the flow asked for instead of always going home.
|
|
17
|
+
- The callback shows a specific message for both new codes.
|
|
18
|
+
|
|
19
|
+
Requires `@seamless-auth/types` 0.25.0.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [419025e]
|
|
24
|
+
- Updated dependencies [0dfe622]
|
|
25
|
+
- @seamless-auth/client@0.2.0
|
|
26
|
+
|
|
27
|
+
## 0.13.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 93a35b7: Put the platform behind ports, and add a bearer transport with token custody to the client core.
|
|
32
|
+
|
|
33
|
+
The client spoke one contract: cookies to a server adapter at `/auth`, with the browser's WebAuthn
|
|
34
|
+
API and `window.location` reached directly. A native binding has none of those, so the pieces that
|
|
35
|
+
differ by platform are now ports a binding supplies, with the browser implementations as the
|
|
36
|
+
defaults. A web application configures nothing and behaves exactly as before.
|
|
37
|
+
- `transport` on `createSeamlessAuthClient` (and `AuthProvider`): cookie transport is unchanged;
|
|
38
|
+
`{ mode: 'bearer', tokenStorage }` makes the client hold the auth API's own tokens. Every request
|
|
39
|
+
carries `x-seamless-auth-transport: bearer`; pre-auth routes carry the ephemeral token that
|
|
40
|
+
`/login` or `/registration/register` returned, kept in memory only; signed-in routes carry the
|
|
41
|
+
access token; the pair a sign-in returns is written through a `TokenStoragePort`; a 401 on a
|
|
42
|
+
signed-in route triggers one `POST /refresh` and one retry, with at most one refresh in flight
|
|
43
|
+
because the auth API revokes the chain on a replayed refresh token. Which routes take which
|
|
44
|
+
token is one table, mirroring the server adapter's, rather than an annotation at each of the
|
|
45
|
+
forty call sites.
|
|
46
|
+
- `PasskeyPort` (`isSupported`, `isPlatformAuthenticatorAvailable`, `create`, `get`) replaces the
|
|
47
|
+
direct SimpleWebAuthn calls at the four ceremony sites. `createBrowserPasskeyPort()` is the
|
|
48
|
+
default. A port reports an authenticator refusal with `PasskeyCeremonyError`, or any error with a
|
|
49
|
+
DOMException `name` and a string `code`, which the client turns into the same result a browser
|
|
50
|
+
failure gave. The PRF helpers no longer depend on SimpleWebAuthn at runtime.
|
|
51
|
+
- `OAuthRedirectPort` replaces `window.location.assign` in the built-in provider buttons.
|
|
52
|
+
`createBrowserOAuthRedirect()` is the default; a port that receives the callback itself resolves
|
|
53
|
+
with `code` and `state`, and the buttons finish the login on the spot.
|
|
54
|
+
- `TokenStoragePort` with `createMemoryTokenStorage()`.
|
|
55
|
+
- `client.authorizedFetch(input, init)` and `useAuthorizedFetch()`: a fetch for the application's
|
|
56
|
+
own API that carries the session the way the transport does (cookies, or the access token with
|
|
57
|
+
one refresh-and-retry on a 401). A path resolves on `apiHost`. This is what a native app uses to
|
|
58
|
+
call routes behind `requireAuth`.
|
|
59
|
+
- `createAuthSession` accepts the client options (or a ready-made `client`) and exposes the client
|
|
60
|
+
it drives as `session.client`. `useAuthClient()` returns that same instance rather than building a
|
|
61
|
+
second one, which bearer transport needs: the client holds the sign-in in flight.
|
|
62
|
+
- `AuthProvider` gains `transport` and `ports` props and exposes `client` and `ports` on the
|
|
63
|
+
context. `usePasskeySupport()` reads the passkey port.
|
|
64
|
+
|
|
65
|
+
Tracks fells-code/seamless-auth-react#124, #125, #127 and #128.
|
|
66
|
+
|
|
67
|
+
### Patch Changes
|
|
68
|
+
|
|
69
|
+
- f8f0dfe: Split the framework-agnostic core into `@seamless-auth/client`, and make this repository an npm workspace that publishes both packages.
|
|
70
|
+
|
|
71
|
+
`@seamless-auth/react` was one package holding two layers: the headless client, session store, and result types that any binding needs, and the React provider, hooks, and screens on top. A React Native binding is next, and it must share the first layer rather than copy it, since the session state machine is the worst place for two implementations to drift (#64).
|
|
72
|
+
|
|
73
|
+
`@seamless-auth/client` now carries `createSeamlessAuthClient`, `createAuthSession`, `SessionStoragePort` and its implementations, `createFetchWithAuth`, the error and result types, the PRF helpers, role matching, and the wire type aliases. `@seamless-auth/react` depends on it and re-exports the same public surface it did before, so an application installing `@seamless-auth/react` sees no change in what it imports or how it behaves.
|
|
74
|
+
|
|
75
|
+
Packaging only: no runtime behaviour changes in either package.
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [93a35b7]
|
|
78
|
+
- Updated dependencies [f8f0dfe]
|
|
79
|
+
- @seamless-auth/client@0.1.0
|
|
80
|
+
|
|
3
81
|
## 0.12.0
|
|
4
82
|
|
|
5
83
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## What It Exports
|
|
12
12
|
|
|
13
13
|
- `AuthProvider`
|
|
14
|
-
- `AuthRoutes`
|
|
14
|
+
- `AuthRoutes`, from `@seamless-auth/react/routes`
|
|
15
15
|
- `useAuth()`
|
|
16
16
|
- `createSeamlessAuthClient()`
|
|
17
17
|
- `useAuthClient()`
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
npm install @seamless-auth/react
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
`react-router-dom` is an optional peer. Install it only if you render the bundled screens from
|
|
33
|
+
`@seamless-auth/react/routes`; `AuthProvider`, the hooks, and the headless client do not need it.
|
|
34
|
+
|
|
32
35
|
## Releases
|
|
33
36
|
|
|
34
37
|
Published versions are listed in [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases. Releases are
|
|
@@ -42,7 +45,7 @@ You can use this package in three ways:
|
|
|
42
45
|
|
|
43
46
|
1. `AuthProvider` + `useAuth()` for auth state and core auth actions
|
|
44
47
|
2. `createSeamlessAuthClient()` or `useAuthClient()` to build fully custom login and registration screens
|
|
45
|
-
3. `AuthRoutes` when you want the built-in login, OTP, magic-link, and passkey screens
|
|
48
|
+
3. `AuthRoutes` (from `@seamless-auth/react/routes`) when you want the built-in login, OTP, magic-link, and passkey screens
|
|
46
49
|
|
|
47
50
|
Most apps will use `AuthProvider` either way.
|
|
48
51
|
|
|
@@ -82,7 +85,8 @@ function Dashboard() {
|
|
|
82
85
|
### Use built-in auth routes with `AuthRoutes`
|
|
83
86
|
|
|
84
87
|
```tsx
|
|
85
|
-
import {
|
|
88
|
+
import { useAuth } from '@seamless-auth/react';
|
|
89
|
+
import { AuthRoutes } from '@seamless-auth/react/routes';
|
|
86
90
|
import { Route, Routes } from 'react-router-dom';
|
|
87
91
|
|
|
88
92
|
function AppRoutes() {
|
|
@@ -102,6 +106,74 @@ function AppRoutes() {
|
|
|
102
106
|
|
|
103
107
|
You are still responsible for your app’s route protection and redirects.
|
|
104
108
|
|
|
109
|
+
`AuthRoutes` moved to its own entry in 0.14.0. Earlier versions exported it from
|
|
110
|
+
`@seamless-auth/react`; update the import to `@seamless-auth/react/routes`.
|
|
111
|
+
|
|
112
|
+
## Next.js
|
|
113
|
+
|
|
114
|
+
The package works in the Next.js App Router. Every module ships with a `'use client'` directive, so
|
|
115
|
+
`AuthProvider` can be rendered straight from a server layout, and nothing touches `window` or
|
|
116
|
+
storage while rendering on the server.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// app/layout.tsx
|
|
120
|
+
import { AuthProvider } from '@seamless-auth/react';
|
|
121
|
+
|
|
122
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
123
|
+
return (
|
|
124
|
+
<html lang="en">
|
|
125
|
+
<body>
|
|
126
|
+
<AuthProvider apiHost={process.env.NEXT_PUBLIC_API_HOST!}>
|
|
127
|
+
{children}
|
|
128
|
+
</AuthProvider>
|
|
129
|
+
</body>
|
|
130
|
+
</html>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Things to know:
|
|
136
|
+
|
|
137
|
+
- **Use custom UI, not `AuthRoutes`.** The bundled screens route with react-router, which a Next.js
|
|
138
|
+
app does not have. Build the screens with `useAuth()`, `useAuthClient()`, and the recipes under
|
|
139
|
+
[Custom UI Recipes](#custom-ui-recipes). `react-router-dom` does not need to be installed.
|
|
140
|
+
- **Props from a server component must be serialisable.** `apiHost`, `magicLinkRedirectUri`, and
|
|
141
|
+
`initialSession` are. `transport` and `ports` take functions, so set those from a client component
|
|
142
|
+
that wraps the provider.
|
|
143
|
+
- **Server code imports from `@seamless-auth/client`.** Everything exported here is a client
|
|
144
|
+
reference inside a server component, including the pure helpers. Read `hasScopedRole`,
|
|
145
|
+
`roleGrantsAccess`, and the types from `@seamless-auth/client` on the server.
|
|
146
|
+
- **Hydration is safe.** The first client render matches the server's: `hasSignedInBefore` is
|
|
147
|
+
`false` on both and turns `true` straight after hydration when the browser remembers a sign-in.
|
|
148
|
+
|
|
149
|
+
### Rendering a known session on the first paint
|
|
150
|
+
|
|
151
|
+
Without help the provider starts in `loading` and learns the session from `/users/me` after
|
|
152
|
+
hydration. A server that has already resolved the session can pass it as `initialSession`, and the
|
|
153
|
+
first paint renders it settled:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<AuthProvider apiHost={apiHost} initialSession={user ? { user } : null}>
|
|
157
|
+
{children}
|
|
158
|
+
</AuthProvider>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Only `user` is required. The provider still revalidates in the background, without reporting
|
|
162
|
+
`loading`, so credentials and organizations fill in, a revoked session signs out, and a session the
|
|
163
|
+
server could not see (for example an expired access cookie with a live refresh cookie) still signs
|
|
164
|
+
in. `initialSession` is read once, when the provider creates its session.
|
|
165
|
+
|
|
166
|
+
Resolve the user on the server with `getSeamlessUser()` from `@seamless-auth/core`, which verifies
|
|
167
|
+
the access cookie locally and asks the auth API directly. It needs your adapter's cookie secret, and
|
|
168
|
+
the session cookies have to reach the Next.js server, so serve the adapter from the same site. Do not forward the browser's cookies to
|
|
169
|
+
your adapter's `/auth/users/me` from the server: when the access cookie has expired the adapter
|
|
170
|
+
rotates the refresh token in a response the browser never receives, and the browser's next refresh
|
|
171
|
+
is then treated as a replay and revokes the session. A dedicated server package for Next.js is tracked in
|
|
172
|
+
[seamless-auth-server#170](https://github.com/fells-code/seamless-auth-server/issues/170).
|
|
173
|
+
|
|
174
|
+
`refreshSession({ background: true })` is public, for revalidating a session your UI already shows
|
|
175
|
+
(on window focus, for example) without flashing a loading state.
|
|
176
|
+
|
|
105
177
|
## `useAuth()` API
|
|
106
178
|
|
|
107
179
|
`useAuth()` returns the current auth state plus the provider-backed helpers:
|
|
@@ -121,7 +193,7 @@ You are still responsible for your app’s route protection and redirects.
|
|
|
121
193
|
listOAuthProviders(): Promise<SeamlessAuthResult<OAuthProvidersResult>>;
|
|
122
194
|
startOAuthLogin(input: StartOAuthLoginInput): Promise<SeamlessAuthResult<StartOAuthLoginResult>>;
|
|
123
195
|
finishOAuthLogin(input: FinishOAuthLoginInput): Promise<SeamlessAuthResult<MessageResult>>;
|
|
124
|
-
refreshSession(): Promise<SeamlessAuthResult<CurrentUserResult>>;
|
|
196
|
+
refreshSession(options?: { background?: boolean }): Promise<SeamlessAuthResult<CurrentUserResult>>;
|
|
125
197
|
refreshStepUpStatus(): Promise<SeamlessAuthResult<StepUpStatus>>;
|
|
126
198
|
verifyStepUpWithPasskey(): Promise<SeamlessAuthResult<StepUpStatus>>;
|
|
127
199
|
verifyStepUpWithPasskeyPrf(input: PasskeyPrfInput): Promise<SeamlessAuthResult<StepUpPrfData>>;
|
|
@@ -197,6 +269,47 @@ result.
|
|
|
197
269
|
Custom UIs get the same default through `useAuthClient()`, and can still override a single send with
|
|
198
270
|
`requestMagicLink(uri)`.
|
|
199
271
|
|
|
272
|
+
### Platform ports and transport
|
|
273
|
+
|
|
274
|
+
Everything that differs between a browser and another platform sits behind a port on
|
|
275
|
+
`AuthProvider`. A web application configures nothing; the browser implementations are the
|
|
276
|
+
defaults. A binding for another platform (React Native is the first) supplies its own:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
<AuthProvider
|
|
280
|
+
apiHost={apiHost}
|
|
281
|
+
transport={{ mode: 'bearer', tokenStorage: keystoreTokenStorage }}
|
|
282
|
+
ports={{ passkeys: nativePasskeys, oauthRedirect: inAppBrowserRedirect }}
|
|
283
|
+
>
|
|
284
|
+
<App />
|
|
285
|
+
</AuthProvider>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
- `transport` selects how the session travels. Cookie transport, the default, is the browser
|
|
289
|
+
contract: the server adapter holds the tokens in `httpOnly` cookies and the client never sees
|
|
290
|
+
them. Bearer transport is the native contract: the client holds the auth API's own tokens,
|
|
291
|
+
presents the one a route needs in `Authorization`, stores the pair a sign-in returns through
|
|
292
|
+
`tokenStorage`, and refreshes once through `POST /auth/refresh` when a request answers 401. At
|
|
293
|
+
most one refresh is in flight at a time, because the auth API treats a replayed refresh token as
|
|
294
|
+
theft and revokes the chain.
|
|
295
|
+
- `ports.passkeys` runs the WebAuthn ceremonies (`create`, `get`, and the two support checks).
|
|
296
|
+
`usePasskeySupport()` and every passkey flow go through it.
|
|
297
|
+
- `ports.oauthRedirect` opens the provider. The browser navigates away and the callback route
|
|
298
|
+
finishes the login; a port that receives the callback itself (an in-app browser session)
|
|
299
|
+
resolves with the `code` and `state`, and the built-in buttons finish the login on the spot.
|
|
300
|
+
|
|
301
|
+
`useAuthClient()` returns the same client instance the provider's session drives. In bearer
|
|
302
|
+
transport that matters: the client holds the sign-in in flight, and a second client would not see it.
|
|
303
|
+
|
|
304
|
+
`useAuthorizedFetch()` (or `client.authorizedFetch`) is a fetch for your own API that carries the
|
|
305
|
+
session the way the transport does: `credentials: 'include'` in cookie transport, the access token
|
|
306
|
+
with one refresh-and-retry on a 401 in bearer transport. A path resolves on `apiHost`.
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const authorizedFetch = useAuthorizedFetch();
|
|
310
|
+
const plan = await authorizedFetch('/api/plan/mine').then(r => r.json());
|
|
311
|
+
```
|
|
312
|
+
|
|
200
313
|
### Scoped roles
|
|
201
314
|
|
|
202
315
|
`hasRole(role)` remains an exact role check. Use `hasScopedRole(role)` for colon-separated scoped
|
|
@@ -1059,8 +1172,10 @@ This package assumes a Seamless Auth-compatible backend with the auth adapter mo
|
|
|
1059
1172
|
|
|
1060
1173
|
- Requests target `${apiHost}/auth/...`
|
|
1061
1174
|
- `apiHost` may be provided with or without a trailing slash
|
|
1062
|
-
-
|
|
1175
|
+
- In cookie transport (the default) requests are sent with `credentials: 'include'`; in bearer
|
|
1176
|
+
transport they carry `x-seamless-auth-transport: bearer` and an `Authorization` header instead
|
|
1063
1177
|
- `AuthProvider` validates the current session by calling `/users/me` on load
|
|
1178
|
+
- Bearer transport additionally uses `POST /refresh`
|
|
1064
1179
|
|
|
1065
1180
|
The built-in flows assume compatible endpoints for:
|
|
1066
1181
|
|
|
@@ -1115,7 +1230,8 @@ refuses every backup-eligible passkey, so handle the code rather than assuming t
|
|
|
1115
1230
|
|
|
1116
1231
|
## Notes
|
|
1117
1232
|
|
|
1118
|
-
- This package does not create its own `<BrowserRouter
|
|
1233
|
+
- This package does not create its own `<BrowserRouter>`, and only `@seamless-auth/react/routes`
|
|
1234
|
+
needs one.
|
|
1119
1235
|
- It is designed to fit into your app’s existing routing tree.
|
|
1120
1236
|
- The quickest path is `AuthProvider` + `AuthRoutes`.
|
|
1121
1237
|
- The most flexible path is `AuthProvider` + custom UI using `useAuth()`, `useAuthClient()`, and `usePasskeySupport()`.
|
package/dist/AuthProvider.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { CurrentUserResult, FinishOAuthLoginInput, FinishOAuthLoginResult, LoginStartResult, MessageResult, OAuthProvidersResult, OrganizationSwitchResult, PasskeyLoginData, PasskeyMetadata, PasskeyRegistrationData, RegisterPasskeyOptions, StartOAuthLoginInput, StartOAuthLoginResult, StepUpPrfData, StepUpStatus } from '
|
|
2
|
-
import type { SeamlessAuthResult } from '
|
|
3
|
-
import { PasskeyPrfInput } from '
|
|
4
|
-
import {
|
|
1
|
+
import { CurrentUserResult, FinishOAuthLoginInput, FinishOAuthLoginResult, LoginStartResult, MessageResult, OAuthProvidersResult, OrganizationSwitchResult, PasskeyLoginData, PasskeyMetadata, PasskeyRegistrationData, RegisterPasskeyOptions, StartOAuthLoginInput, StartOAuthLoginResult, StepUpPrfData, StepUpStatus } from '@seamless-auth/client';
|
|
2
|
+
import type { SeamlessAuthResult } from '@seamless-auth/client';
|
|
3
|
+
import { PasskeyPrfInput } from '@seamless-auth/client';
|
|
4
|
+
import { type InitialSession, type RefreshSessionOptions, type OAuthRedirectPort, type PasskeyPort, type SeamlessAuthClient, type TransportOptions } from '@seamless-auth/client';
|
|
5
|
+
import { Credential, Organization, User } from '@seamless-auth/client';
|
|
5
6
|
import React, { ReactNode } from 'react';
|
|
6
7
|
export interface AuthContextType {
|
|
7
8
|
user: User | null;
|
|
8
9
|
logout: () => Promise<SeamlessAuthResult<MessageResult>>;
|
|
9
10
|
logoutAllSessions: () => Promise<SeamlessAuthResult<MessageResult>>;
|
|
10
11
|
deleteUser: () => Promise<SeamlessAuthResult<MessageResult>>;
|
|
11
|
-
refreshSession: () => Promise<SeamlessAuthResult<CurrentUserResult>>;
|
|
12
|
+
refreshSession: (options?: RefreshSessionOptions) => Promise<SeamlessAuthResult<CurrentUserResult>>;
|
|
12
13
|
isAuthenticated: boolean;
|
|
13
14
|
hasRole: (role: string) => boolean | undefined;
|
|
14
15
|
hasScopedRole: (role: string | string[]) => boolean | undefined;
|
|
@@ -34,6 +35,18 @@ export interface AuthContextType {
|
|
|
34
35
|
verifyStepUpWithPasskeyPrf: (input: PasskeyPrfInput) => Promise<SeamlessAuthResult<StepUpPrfData>>;
|
|
35
36
|
verifyStepUpWithTotp: (code: string) => Promise<SeamlessAuthResult<StepUpStatus>>;
|
|
36
37
|
loading: boolean;
|
|
38
|
+
/** The client behind the session. `useAuthClient()` returns this same instance. */
|
|
39
|
+
client: SeamlessAuthClient;
|
|
40
|
+
/** The platform ports the built-in screens and hooks go through. */
|
|
41
|
+
ports: AuthPorts;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* What a binding plugs in for its platform. The browser defaults cover a web
|
|
45
|
+
* application; a native binding supplies its own.
|
|
46
|
+
*/
|
|
47
|
+
export interface AuthPorts {
|
|
48
|
+
passkeys: PasskeyPort;
|
|
49
|
+
oauthRedirect: OAuthRedirectPort;
|
|
37
50
|
}
|
|
38
51
|
/**
|
|
39
52
|
* Returns the current context
|
|
@@ -49,6 +62,21 @@ interface AuthProviderProps {
|
|
|
49
62
|
* send and a resend read it from here, so the two cannot drift apart.
|
|
50
63
|
*/
|
|
51
64
|
magicLinkRedirectUri?: string;
|
|
65
|
+
/**
|
|
66
|
+
* How the session travels to the server adapter. Defaults to cookie
|
|
67
|
+
* transport, which is what a browser application wants.
|
|
68
|
+
*/
|
|
69
|
+
transport?: Omit<TransportOptions, 'apiHost'>;
|
|
70
|
+
/** Platform ports. Each one left out falls back to the browser's. */
|
|
71
|
+
ports?: Partial<AuthPorts>;
|
|
72
|
+
/**
|
|
73
|
+
* The session a server already resolved for this request, or `null` when it
|
|
74
|
+
* found none. The first paint renders it settled instead of loading, and the
|
|
75
|
+
* provider then revalidates in the background, which fills in credentials and
|
|
76
|
+
* organizations and catches a session the server could not see. Read once,
|
|
77
|
+
* when the session is created.
|
|
78
|
+
*/
|
|
79
|
+
initialSession?: InitialSession | null;
|
|
52
80
|
}
|
|
53
81
|
export declare const AuthProvider: React.FC<AuthProviderProps>;
|
|
54
82
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths for the bundled auth screens.
|
|
3
|
+
*
|
|
4
|
+
* Kebab-case throughout, except for two paths that are dictated by contracts
|
|
5
|
+
* outside this package rather than by the naming convention:
|
|
6
|
+
*
|
|
7
|
+
* - `verifyMagicLink` is the URL the auth API builds when it emails a magic
|
|
8
|
+
* link, so it has to match that value exactly.
|
|
9
|
+
* - `oauthCallback` is registered with OAuth providers as an allowed redirect
|
|
10
|
+
* URI, so renaming it would break configured integrations.
|
|
11
|
+
*/
|
|
12
|
+
export declare const authRoutePaths: {
|
|
13
|
+
readonly login: "/login";
|
|
14
|
+
readonly passkeyLogin: "/passkey-login";
|
|
15
|
+
readonly verifyPhoneOtp: "/verify-phone-otp";
|
|
16
|
+
readonly verifyEmailOtp: "/verify-email-otp";
|
|
17
|
+
readonly verifyMagicLink: "/verify-magiclink";
|
|
18
|
+
readonly oauthCallback: "/oauth/callback";
|
|
19
|
+
readonly registerPasskey: "/register-passkey";
|
|
20
|
+
readonly magicLinkSent: "/magic-link-sent";
|
|
21
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import{createContext as t,useContext as e,useMemo as s,useRef as a,useSyncExternalStore as r,useEffect as i,useState as o}from"react";import{jsx as n}from"react/jsx-runtime";import{createBrowserOAuthRedirect as c,createBrowserPasskeyPort as l,createAuthSession as u}from"@seamless-auth/client";const p=t(void 0),d=()=>{const t=e(p);if(!t)throw new Error("useAuth must be used within an AuthProvider");return t},h=({children:t,apiHost:e,autoDetectPreviousSignin:o=!0,magicLinkRedirectUri:d,transport:h,ports:g,initialSession:k})=>{const y=s(()=>({passkeys:g?.passkeys??l(),oauthRedirect:g?.oauthRedirect??c()}),[g?.passkeys,g?.oauthRedirect]),{mode:m,basePath:S,tokenStorage:f,fetch:v}=h??{},b=s(()=>h?{mode:m,basePath:S,tokenStorage:f,fetch:v}:void 0,[m,S,f,v]),P=a(k),R=s(()=>u({apiHost:e,magicLinkRedirectUri:d,transport:b,passkeys:y.passkeys,detectPreviousSignIn:o,initialSession:P.current}),[e,d,b,y.passkeys,o]),w=r(R.subscribe,R.getState,R.getServerState);i(()=>{R.actions.refreshSession({background:void 0!==P.current})},[R]);const A=s(()=>({...w,...R.actions,apiHost:e,magicLinkRedirectUri:d,client:R.client,ports:y}),[w,R,e,d,y]);return n(p.Provider,{value:A,children:t})},g=()=>d().client,k=["passkey","magic_link"],y=()=>{const t=g(),[e,s]=o(null),[a,r]=o(!0);return i(()=>{let e=!0;return(async()=>{try{const{data:a,error:r}=await t.getPublicSystemConfig();e&&!r&&a?.loginMethods?.length&&s(a.loginMethods)}catch{}finally{e&&r(!1)}})(),()=>{e=!1}},[t]),{loginMethods:e,loading:a}},m=t=>Boolean(t?.some(t=>"passkey"!==t)),S=()=>{const{ports:t}=d(),[e,s]=o(!1),[a,r]=o(!0);return i(()=>{let e=!0;return(async()=>{try{const a=t.passkeys.isSupported()&&await t.passkeys.isPlatformAuthenticatorAvailable();e&&s(a)}catch{e&&s(!1)}finally{e&&r(!1)}})(),()=>{e=!1}},[t.passkeys]),{passkeySupported:e,loading:a}};export{h as A,k as F,g as a,y as b,S as c,m as h,d as u};
|
|
3
|
+
//# sourceMappingURL=shared-BzrBuR0z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-BzrBuR0z.js","sources":["../../src/AuthProvider.tsx","../../src/hooks/useAuthClient.ts","../../src/hooks/useLoginMethods.ts","../../src/hooks/usePasskeySupport.ts"],"sourcesContent":[null,null,null,null],"names":["AuthContext","createContext","undefined","useAuth","context","useContext","Error","AuthProvider","children","apiHost","autoDetectPreviousSignin","magicLinkRedirectUri","transport","ports","providedPorts","initialSession","useMemo","passkeys","createBrowserPasskeyPort","oauthRedirect","createBrowserOAuthRedirect","mode","basePath","tokenStorage","fetch","fetchImpl","stableTransport","initialSessionRef","useRef","session","createAuthSession","detectPreviousSignIn","current","state","useSyncExternalStore","subscribe","getState","getServerState","useEffect","actions","refreshSession","background","value","client","_jsx","Provider","useAuthClient","FALLBACK_LOGIN_METHODS","useLoginMethods","authClient","loginMethods","setLoginMethods","useState","loading","setLoading","active","async","data","error","getPublicSystemConfig","length","read","hasNonPasskeyLoginMethod","Boolean","some","method","usePasskeySupport","passkeySupported","setPasskeySupported","supported","isSupported","isPlatformAuthenticatorAvailable","checkSupport"],"mappings":";sSA4GA,MAAMA,EAAcC,OAA2CC,GAMlDC,EAAU,KACrB,MAAMC,EAAUC,EAAWL,GAC3B,IAAKI,EACH,MAAM,IAAIE,MAAM,+CAElB,OAAOF,GA6BIG,EAA4C,EACvDC,WACAC,UACAC,4BAA2B,EAC3BC,uBACAC,YACAC,MAAOC,EACPC,qBAKA,MAAMF,EAAQG,EACZ,KAAA,CACEC,SAAUH,GAAeG,UAAYC,IACrCC,cAAeL,GAAeK,eAAiBC,MAEjD,CAACN,GAAeG,SAAUH,GAAeK,iBAGrCE,KAAEA,EAAIC,SAAEA,EAAQC,aAAEA,EAAcC,MAAOC,GAAcb,GAAa,CAAA,EAClEc,EAAkBV,EACtB,IAAOJ,EAAY,CAAES,OAAMC,WAAUC,eAAcC,MAAOC,QAAcvB,EAExE,CAACmB,EAAMC,EAAUC,EAAcE,IAM3BE,EAAoBC,EAAOb,GAE3Bc,EAAUb,EACd,IACEc,EAAkB,CAChBrB,UACAE,uBACAC,UAAWc,EACXT,SAAUJ,EAAMI,SAChBc,qBAAsBrB,EACtBK,eAAgBY,EAAkBK,UAEtC,CACEvB,EACAE,EACAe,EACAb,EAAMI,SACNP,IAOEuB,EAAQC,EACZL,EAAQM,UACRN,EAAQO,SACRP,EAAQQ,gBAcVC,EAAU,KACHT,EAAQU,QAAQC,eAAe,CAClCC,gBAA0CvC,IAA9ByB,EAAkBK,WAE/B,CAACH,IAEJ,MAAMa,EAAQ1B,EACZ,KAAA,IACKiB,KACAJ,EAAQU,QACX9B,UACAE,uBACAgC,OAAQd,EAAQc,OAChB9B,UAEF,CAACoB,EAAOJ,EAASpB,EAASE,EAAsBE,IAGlD,OAAO+B,EAAC5C,EAAY6C,SAAQ,CAACH,MAAOA,EAAKlC,SAAGA,KChOjCsC,EAAgB,IACpB3C,IAAUwC,OCINI,EAAwC,CAAC,UAAW,cAWpDC,EAAkB,KAC7B,MAAMC,EAAaH,KACZI,EAAcC,GAAmBC,EAA+B,OAChEC,EAASC,GAAcF,GAAS,GA8BvC,OA5BAd,EAAU,KACR,IAAIiB,GAAS,EAsBb,MApBaC,WACX,IACE,MAAMC,KAAEA,EAAIC,MAAEA,SAAgBT,EAAWU,wBAErCJ,IAAWG,GAASD,GAAMP,cAAcU,QAC1CT,EAAgBM,EAAKP,aAEzB,CAAE,MAIF,SACMK,GACFD,GAAW,EAEf,GAGGO,GAEE,KACLN,GAAS,IAEV,CAACN,IAEG,CAAEC,eAAcG,YAUZS,EAA4BZ,GACvCa,QAAQb,GAAcc,KAAKC,GAAqB,YAAXA,IC/D1BC,EAAoB,KAC/B,MAAMrD,MAAEA,GAAUV,KACXgE,EAAkBC,GAAuBhB,GAAS,IAClDC,EAASC,GAAcF,GAAS,GA+BvC,OA7BAd,EAAU,KACR,IAAIiB,GAAS,EAuBb,MArBqBC,WACnB,IACE,MAAMa,EACJxD,EAAMI,SAASqD,qBACRzD,EAAMI,SAASsD,mCACpBhB,GACFa,EAAoBC,EAExB,CAAE,MACId,GACFa,GAAoB,EAExB,SACMb,GACFD,GAAW,EAEf,GAGGkB,GAEE,KACLjB,GAAS,IAEV,CAAC1C,EAAMI,WAEH,CAAEkD,mBAAkBd"}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The client behind the provider's session. The same instance the store
|
|
3
|
+
* drives, not a second one: in bearer transport the client holds the sign-in
|
|
4
|
+
* in flight, and a second client would not see it.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useAuthClient: () => import("@seamless-auth/client").SeamlessAuthClient;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fetch for the application's own API that carries the session the way the
|
|
3
|
+
* provider's transport does: cookies for a web application, the access token
|
|
4
|
+
* (refreshed once on a 401) for a native one. A path resolves on `apiHost`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useAuthorizedFetch: () => (input: string | URL, init?: RequestInit) => Promise<Response>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { AuthContextType, AuthProvider, useAuth } from './AuthProvider';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { encodePrfSalt, extractPasskeyPrfResult, isPasskeyPrfSupported, PasskeyPrfInput, PasskeyPrfResult } from './client/webauthnPrf';
|
|
1
|
+
import { AuthContextType, AuthPorts, AuthProvider, useAuth } from './AuthProvider';
|
|
2
|
+
import { createSeamlessAuthClient, CredentialUpdateResult, CreateOrganizationInput, CurrentUserResult, FinishOAuthLoginInput, FinishOAuthLoginResult, LoginInput, LoginMethod, LoginStartResult, MessageResult, OAuthProvider, OAuthProvidersResult, PublicSystemConfigResult, OrganizationMemberInput, OrganizationMemberUpdateInput, OrganizationMembersResult, OrganizationMembershipResult, OrganizationResult, OrganizationSwitchResult, OrganizationsResult, PasskeyLoginData, PasskeyAttachment, PasskeyMetadata, PasskeyRegistrationData, RegisterInput, RegisterPasskeyOptions, SeamlessAuthClient, SeamlessAuthClientOptions, StartOAuthLoginInput, StartOAuthLoginResult, StepUpMethod, StepUpStatus, StepUpPrfData, TotpEnrollmentStartResult, TotpStatus, UpdateOrganizationInput } from '@seamless-auth/client';
|
|
3
|
+
import { getOAuthErrorCode, getPasskeyPolicyErrorCode, getWebAuthnErrorDetail, isUnauthenticated, OAuthErrorCode, PasskeyPolicyErrorCode, SeamlessAuthError, WebAuthnErrorDetail } from '@seamless-auth/client';
|
|
4
|
+
import type { SeamlessAuthResult } from '@seamless-auth/client';
|
|
5
|
+
import { encodePrfSalt, extractPasskeyPrfResult, isPasskeyPrfSupported, PasskeyPrfInput, PasskeyPrfResult } from '@seamless-auth/client';
|
|
7
6
|
import { useAuthClient } from './hooks/useAuthClient';
|
|
7
|
+
import { useAuthorizedFetch } from './hooks/useAuthorizedFetch';
|
|
8
8
|
import { hasNonPasskeyLoginMethod, useLoginMethods } from './hooks/useLoginMethods';
|
|
9
9
|
import { usePasskeySupport } from './hooks/usePasskeySupport';
|
|
10
|
-
import { hasScopedRole, roleGrantsAccess } from '
|
|
11
|
-
import { Credential, Organization, OrganizationMembership, User } from '
|
|
12
|
-
|
|
13
|
-
export
|
|
10
|
+
import { hasScopedRole, roleGrantsAccess } from '@seamless-auth/client';
|
|
11
|
+
import { Credential, Organization, OrganizationMembership, User } from '@seamless-auth/client';
|
|
12
|
+
import type { InitialSession, OAuthRedirectOutcome, OAuthRedirectPort, PasskeyPort, StoredTokens, TokenStoragePort, RefreshSessionOptions, TransportOptions } from '@seamless-auth/client';
|
|
13
|
+
export { AuthProvider, createSeamlessAuthClient, encodePrfSalt, extractPasskeyPrfResult, getOAuthErrorCode, getPasskeyPolicyErrorCode, getWebAuthnErrorDetail, hasNonPasskeyLoginMethod, hasScopedRole, isPasskeyPrfSupported, isUnauthenticated, roleGrantsAccess, SeamlessAuthError, useAuth, useAuthClient, useAuthorizedFetch, useLoginMethods, usePasskeySupport, };
|
|
14
|
+
export type { CredentialUpdateResult, AuthContextType, AuthPorts, Credential, CreateOrganizationInput, CurrentUserResult, FinishOAuthLoginInput, FinishOAuthLoginResult, InitialSession, LoginInput, LoginMethod, LoginStartResult, MessageResult, OAuthErrorCode, OAuthProvider, OAuthProvidersResult, OAuthRedirectOutcome, OAuthRedirectPort, Organization, OrganizationMemberInput, OrganizationMembership, OrganizationMemberUpdateInput, OrganizationMembersResult, OrganizationMembershipResult, OrganizationResult, OrganizationSwitchResult, OrganizationsResult, PasskeyLoginData, PasskeyAttachment, PasskeyMetadata, PasskeyPolicyErrorCode, PasskeyPort, PasskeyPrfInput, PasskeyPrfResult, PasskeyRegistrationData, PublicSystemConfigResult, RefreshSessionOptions, RegisterInput, RegisterPasskeyOptions, SeamlessAuthClient, SeamlessAuthClientOptions, SeamlessAuthResult, StartOAuthLoginInput, StartOAuthLoginResult, StepUpMethod, StepUpStatus, StepUpPrfData, StoredTokens, TokenStoragePort, TotpEnrollmentStartResult, TotpStatus, TransportOptions, UpdateOrganizationInput, User, WebAuthnErrorDetail, };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{browserSupportsWebAuthn as n,bufferToBase64URLString as o,base64URLStringToBuffer as i,startAuthentication as a,startRegistration as s,WebAuthnError as l}from"@simplewebauthn/browser";import{createContext as c,useMemo as d,useSyncExternalStore as u,useEffect as m,useContext as _,useState as g,useRef as p,useCallback as f}from"react";import{useHref as h,useNavigate as y,useLocation as b,useSearchParams as v,Routes as k,Route as w,Navigate as x}from"react-router-dom";import P from"libphonenumber-js";class S extends Error{constructor(e,t,r,n){super(e),this.name="SeamlessAuthError",this.status=t,this.body=r,this.cause=n}}const O={oauth_missing_email:!0,oauth_email_not_verified:!0,oauth_missing_subject:!0};function N(e){if("object"==typeof e&&null!==e)return e.code}function T(e){if(!(e instanceof S))return;if("object"!=typeof e.body||null===e.body)return;const t=N(e.body)??N(e.body.details);return"string"==typeof t&&Object.prototype.hasOwnProperty.call(O,t)?t:void 0}const C={attachment_not_allowed:!0,synced_passkey_not_allowed:!0,authenticator_not_allowed:!0,prf_required:!0};function I(e){if("object"!=typeof e||null===e)return;const t=e.error;return"string"==typeof t&&Object.prototype.hasOwnProperty.call(C,t)?t:void 0}function A(e){if(e instanceof S&&"object"==typeof e.body&&null!==e.body)return I(e.body)??I(e.body.details)}function M(e){return e instanceof S&&401===e.status}function E(e){if(!(e instanceof S)||("object"!=typeof(t=e.cause)||null===t||"string"!=typeof t.name))return;var t;const{name:r,code:n,message:o}=e.cause;return{name:r,code:"string"==typeof n?n:void 0,message:"string"==typeof o?o:""}}async function z(e,t){let r;try{r=await e.json()}catch{r=void 0}return new S(function(e){if("object"!=typeof e||null===e)return;const{error:t,message:r}=e;return"string"==typeof t&&t?t:"string"==typeof r&&r?r:void 0}(r)??t,e.status,r)}async function F(e){try{return await e.json()}catch{return}}async function U(e,t){let r;try{r=await e}catch{return{data:null,error:new S(t,0)}}return r.ok?{data:await F(r),error:null}:{data:null,error:await z(r,t)}}function L(e){return{data:e,error:null}}function R(e,t=0,r,n){return{data:null,error:new S(e,t,r,n)}}function q(e){if(e instanceof ArrayBuffer)return e;const t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),r=new Uint8Array(t.byteLength);return r.set(t),r.buffer}function B(e){return"string"==typeof e?e:o(q(e))}function j(e){return"string"==typeof e?i(e):e}function J(e){return{...e.credentialId?{credentialId:e.credentialId}:{},prf:{salt:B(e.salt),...e.secondSalt?{secondSalt:B(e.secondSalt)}:{}}}}async function G(){return!!n()&&("undefined"==typeof window||!1!==window.isSecureContext)&&void 0!==globalThis.PublicKeyCredential&&"undefined"!=typeof navigator&&"function"==typeof navigator.credentials?.get}function W(e){const t=e,r=t.extensions?.prf?.eval;return r?{...t,extensions:{...t.extensions,prf:{...t.extensions?.prf,eval:{...r,...r.first?{first:j(r.first)}:{},...r.second?{second:j(r.second)}:{}}}}}:e}function $(e){const t=e.clientExtensionResults,r=t?.prf?.results?.first;if(!r)return null;const n=(i=r)instanceof ArrayBuffer?new Uint8Array(i):new Uint8Array(i.buffer,i.byteOffset,i.byteLength);var i;return{credentialId:e.id,output:n,outputBase64url:o(q(n))}}function D(e){const t=e.clientExtensionResults;return t?.prf?.results?{...e,clientExtensionResults:{...e.clientExtensionResults,prf:{...t.prf,results:void 0}}}:e}function V(e,t){const r=e.method===t?e.method:null;return"Success"===e.message&&!0===e.fresh&&null!==r?L({fresh:!0,method:r,verifiedAt:e.verifiedAt??null,expiresAt:e.expiresAt??null,maxAgeSeconds:e.maxAgeSeconds??0}):R(e.message??"Step-up authentication failed.")}function H(e){return e?{method:"POST",body:JSON.stringify(J(e))}:{method:"POST"}}function K(e,t,r){const n=R(t,0,void 0,r),o=E(n.error);return console.error(e,o?.name??"Unknown error."),n}const Y=e=>{const t=(e=>{const{authHost:t}=e;return async function(e,r){const n=`${t?.replace(/\/+$/,"")??""}/auth${e.startsWith("/")?e:`/${e}`}`,o=null!=r?.body,i={...r,credentials:"include",headers:{...o?{"Content-Type":"application/json"}:{},...r?.headers}};return fetch(n,i)}})({authHost:e.apiHost});return{getCurrentUser:()=>U(t("users/me",{method:"GET"}),"Failed to load the current user."),login:e=>U(t("/login",{method:"POST",body:JSON.stringify(e)}),"Failed to start sign-in."),loginWithPasskey:async e=>{const r=await U(t("/webAuthn/login/start",{...H(e?.prf)}),"Failed to start passkey login.");if(r.error)return r;let n,o;try{const e=await a({optionsJSON:W(r.data)});n=$(e),o=D(e)}catch(e){return K("Passkey login error.","Passkey login failed.",e)}const i=await U(t("/webAuthn/login/finish",{method:"POST",body:JSON.stringify({assertionResponse:o})}),"Failed to verify passkey.");return i.error?i:"Success"!==i.data?.message?R(i.data?.message??"Passkey login failed."):L(n?{prf:n}:{})},logout:(e={})=>U(t("all_sessions"===e.scope?"/logout/all":"/logout",{method:"DELETE"}),"Failed to sign out."),logoutAllSessions:()=>U(t("/logout/all",{method:"DELETE"}),"Failed to sign out of all sessions."),deleteUser:()=>U(t("/users/delete",{method:"DELETE"}),"Failed to delete the account."),register:e=>U(t("/registration/register",{method:"POST",body:JSON.stringify({email:e.email,...e.phone?{phone:e.phone}:{}})}),"Failed to register."),requestPhoneOtp:()=>U(t("/otp/generate-phone-otp",{method:"POST",body:JSON.stringify({})}),"Failed to send the SMS code."),verifyPhoneOtp:e=>U(t("/otp/verify-phone-otp",{method:"POST",body:JSON.stringify({verificationToken:e})}),"Failed to verify the SMS code."),requestLoginPhoneOtp:()=>U(t("/otp/generate-login-phone-otp",{method:"POST",body:JSON.stringify({})}),"Failed to send the SMS code."),verifyLoginPhoneOtp:e=>U(t("/otp/verify-login-phone-otp",{method:"POST",body:JSON.stringify({verificationToken:e})}),"Failed to verify the SMS code."),requestEmailOtp:()=>U(t("/otp/generate-email-otp",{method:"POST",body:JSON.stringify({})}),"Failed to send the email code."),verifyEmailOtp:e=>U(t("/otp/verify-email-otp",{method:"POST",body:JSON.stringify({verificationToken:e})}),"Failed to verify the email code."),requestLoginEmailOtp:()=>U(t("/otp/generate-login-email-otp",{method:"POST",body:JSON.stringify({})}),"Failed to send the email code."),verifyLoginEmailOtp:e=>U(t("/otp/verify-login-email-otp",{method:"POST",body:JSON.stringify({verificationToken:e})}),"Failed to verify the email code."),requestMagicLink:r=>{const n=r??e.magicLinkRedirectUri;return U(t("/magic-link",{method:"POST",body:JSON.stringify(n?{redirectUri:n}:{})}),"Failed to send the magic link.")},checkMagicLink:()=>U(t("/magic-link/check",{method:"GET"}),"Failed to check the magic link."),verifyMagicLink:e=>U(t(`/magic-link/verify/${encodeURIComponent(e)}`,{method:"GET"}),"Failed to verify the magic link."),listOAuthProviders:()=>U(t("/oauth/providers",{method:"GET"}),"Failed to list OAuth providers."),getPublicSystemConfig:()=>U(t("/system-config/public",{method:"GET"}),"Failed to read the public system configuration."),startOAuthLogin:e=>U(t(`/oauth/${encodeURIComponent(e.providerId)}/start`,{method:"POST",body:JSON.stringify({...e.redirectUri?{redirectUri:e.redirectUri}:{},...e.returnTo?{returnTo:e.returnTo}:{}})}),"Failed to start OAuth login."),finishOAuthLogin:e=>U(t(`/oauth/${encodeURIComponent(e.providerId)}/callback`,{method:"POST",body:JSON.stringify({code:e.code,state:e.state})}),"Failed to finish OAuth login."),registerPasskey:async e=>{const r=function(e){return"metadata"in e?e:{metadata:e}}(e),n=await U(t(function(e){const t=new URLSearchParams;e.requirePrf?t.set("requirePrf","true"):e.requestPrf&&t.set("requestPrf","true"),e.attachment&&t.set("attachment",e.attachment);const r=t.toString();return"/webAuthn/register/start"+(r?`?${r}`:"")}(r),{method:"GET"}),"Failed to fetch passkey registration challenge.");if(n.error)return n;let o;try{o=await s({optionsJSON:n.data})}catch(e){return e instanceof l?R(e.name,0,void 0,e):K("Passkey registration error.","Passkey registration failed.",e)}const i=function(e){const t=e.clientExtensionResults;return!0===t?.prf?.enabled}(o),a=await U(t("/webAuthn/register/finish",{method:"POST",body:JSON.stringify({attestationResponse:o,metadata:{...r.metadata,prfCapable:i}})}),"Verification failed.");return a.error?a:L({credentialId:o.id,prfCapable:i})},isPasskeyPrfSupported:G,getStepUpStatus:()=>U(t("/step-up/status",{method:"GET"}),"Failed to load step-up status."),verifyStepUpWithPasskey:async()=>{const e=await U(t("/step-up/webauthn/start",{method:"POST"}),"Failed to start step-up authentication.");if(e.error)return e;let r;try{r=D(await a({optionsJSON:W(e.data)}))}catch(e){return K("Step-up authentication error.","Step-up authentication failed.",e)}const n=await U(t("/step-up/webauthn/finish",{method:"POST",body:JSON.stringify({assertionResponse:r})}),"Failed to verify step-up authentication.");return n.error?n:V(n.data??{},"webauthn")},verifyStepUpWithPasskeyPrf:async e=>{const r=await U(t("/step-up/webauthn/start",{...H(e)}),"Failed to start step-up authentication.");if(r.error)return r;let n,o;try{const e=await a({optionsJSON:W(r.data)});n=$(e),o=D(e)}catch(e){return K("Step-up authentication error.","Step-up authentication failed.",e)}if(!n)return R("Passkey did not return PRF output.");const i=await U(t("/step-up/webauthn/finish",{method:"POST",body:JSON.stringify({assertionResponse:o})}),"Failed to verify step-up authentication.");if(i.error)return i;const s=V(i.data??{},"webauthn");return s.error?s:L({...s.data,credentialId:n.credentialId,prf:n})},getTotpStatus:()=>U(t("/totp/status",{method:"GET"}),"Failed to load authenticator status."),startTotpEnrollment:()=>U(t("/totp/enroll/start",{method:"POST"}),"Failed to start authenticator enrollment."),verifyTotpEnrollment:e=>U(t("/totp/enroll/verify",{method:"POST",body:JSON.stringify({code:e})}),"Failed to verify the authenticator code."),disableTotp:e=>U(t("/totp/disable",{method:"POST",body:JSON.stringify({code:e})}),"Failed to disable the authenticator."),verifyStepUpWithTotp:async e=>{const r=await U(t("/totp/verify-mfa",{method:"POST",body:JSON.stringify({code:e})}),"Failed to verify step-up authentication.");return r.error?r:V(r.data??{},"totp")},updateCredential:e=>U(t("users/credentials",{method:"POST",body:JSON.stringify(e)}),"Failed to update credential."),deleteCredential:e=>U(t("users/credentials",{method:"DELETE",body:JSON.stringify({id:e})}),"Failed to delete credential."),listOrganizations:()=>U(t("/organizations",{method:"GET"}),"Failed to list organizations."),createOrganization:e=>U(t("/organizations",{method:"POST",body:JSON.stringify(e)}),"Failed to create organization."),getOrganization:e=>U(t(`/organizations/${encodeURIComponent(e)}`,{method:"GET"}),"Failed to load organization."),updateOrganization:(e,r)=>U(t(`/organizations/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify(r)}),"Failed to update organization."),switchOrganization:e=>U(t(`/organizations/${encodeURIComponent(e)}/switch`,{method:"POST"}),"Failed to switch organization."),listOrganizationMembers:e=>U(t(`/organizations/${encodeURIComponent(e)}/members`,{method:"GET"}),"Failed to list organization members."),addOrganizationMember:(e,r)=>U(t(`/organizations/${encodeURIComponent(e)}/members`,{method:"POST",body:JSON.stringify(r)}),"Failed to add organization member."),updateOrganizationMember:(e,r,n)=>U(t(`/organizations/${encodeURIComponent(e)}/members/${encodeURIComponent(r)}`,{method:"PATCH",body:JSON.stringify(n)}),"Failed to update organization member."),removeOrganizationMember:(e,r)=>U(t(`/organizations/${encodeURIComponent(e)}/members/${encodeURIComponent(r)}`,{method:"DELETE"}),"Failed to remove organization member.")}};function Z(e,t){const r=e.trim(),n=t.trim();if(!r||!n)return!1;if(r===n)return!0;if(r.endsWith(":*")){const e=r.slice(0,-2);return n===e||n.startsWith(`${e}:`)}if(!n.includes(":"))return!1;if(!r.includes(":"))return n.startsWith(`${r}:`);const o=r.split(":"),i=n.split(":"),a=o[o.length-1],s=i[i.length-1],l=o.slice(0,-1),c=i.slice(0,-1);return"write"===a&&"read"===s&&l.length===c.length&&l.every((e,t)=>e===c[t])}function Q(e,t){if(!Array.isArray(e))return!1;const r=e.filter(e=>"string"==typeof e);return(Array.isArray(t)?t:[t]).some(e=>r.some(t=>Z(t,e)))}function X(){return"undefined"==typeof localStorage?function(){const e=new Map;return{get:t=>e.get(t)??null,set:(t,r)=>{e.set(t,r)}}}():{get:e=>{try{return localStorage.getItem(e)}catch{return null}},set:(e,t)=>{try{localStorage.setItem(e,t)}catch{}}}}const ee="seamlessauth_seen",te={user:null,credentials:[],organizations:[],activeOrganization:null,stepUpStatus:null,isAuthenticated:!1};const re=c(void 0),ne=()=>{const e=_(re);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e},oe=({children:t,apiHost:r,autoDetectPreviousSignin:n=!0,magicLinkRedirectUri:o})=>{const i=d(()=>function(e){const{apiHost:t,detectPreviousSignIn:r=!0}=e,n=Y({apiHost:t}),o=e.storage??X(),i=new Set;let a=!1,s=0,l={...te,loading:!0,hasSignedInBefore:r&&"true"===o.get(ee)};function c(e){const t={...l,...e};Object.keys(e).some(e=>l[e]!==t[e])&&(l=t,i.forEach(e=>e()))}function d(){o.set(ee,"true"),r&&c({hasSignedInBefore:!0})}function u(){c(te)}async function m(){const e=++s;c({loading:!0});const t=await n.getCurrentUser();return a||e!==s?t:t.error?(u(),c({loading:!1}),t):(c({user:t.data.user,credentials:t.data.credentials??[],organizations:t.data.organizations??[],activeOrganization:t.data.activeOrganization??null,isAuthenticated:!0,loading:!1}),l.hasSignedInBefore||d(),t)}async function _(e){const t=await e();return t.error||await m(),t}return{getState:()=>l,subscribe:e=>(i.add(e),()=>{i.delete(e)}),actions:{login:(e,t)=>n.login({identifier:e,passkeyAvailable:t}),handlePasskeyLogin:()=>_(()=>n.loginWithPasskey()),registerPasskey:e=>_(()=>n.registerPasskey(e)),refreshSession:m,logout:async function(){try{return await n.logout()}finally{u()}},logoutAllSessions:async function(){try{return await n.logoutAllSessions()}finally{u()}},deleteUser:async()=>{const e=await n.deleteUser();return e.error||u(),e},updateCredential:async e=>{const{data:t,error:r}=await n.updateCredential({friendlyName:e.friendlyName??null,id:e.id});if(r)return{data:null,error:r};const o=t.credential;return c({credentials:l.credentials.map(e=>e.id===o.id?{...e,...o}:e)}),{data:o,error:null}},deleteCredential:async e=>{const t=await n.deleteCredential(e);return t.error||c({credentials:l.credentials.filter(t=>t.id!==e)}),t},switchOrganization:e=>_(()=>n.switchOrganization(e)),listOAuthProviders:()=>n.listOAuthProviders(),startOAuthLogin:e=>n.startOAuthLogin(e),finishOAuthLogin:e=>_(()=>n.finishOAuthLogin(e)),refreshStepUpStatus:async()=>{const e=await n.getStepUpStatus();return c({stepUpStatus:e.error?null:e.data}),e},verifyStepUpWithPasskey:async()=>{const e=await n.verifyStepUpWithPasskey();return e.error||c({stepUpStatus:e.data}),e},verifyStepUpWithPasskeyPrf:async e=>{const t=await n.verifyStepUpWithPasskeyPrf(e);return t.error||c({stepUpStatus:{fresh:t.data.fresh,method:t.data.method,verifiedAt:t.data.verifiedAt,expiresAt:t.data.expiresAt,maxAgeSeconds:t.data.maxAgeSeconds}}),t},verifyStepUpWithTotp:async e=>{const t=await n.verifyStepUpWithTotp(e);return t.error||c({stepUpStatus:t.data}),t},hasRole:e=>l.user?.roles?.includes(e),hasScopedRole:e=>l.user?Q(l.user.roles,e):void 0,markSignedIn:d},destroy:()=>{a=!0,i.clear()}}}({apiHost:r,detectPreviousSignIn:n}),[r,n]),a=u(i.subscribe,i.getState,i.getState);m(()=>{i.actions.refreshSession()},[i]);const s=d(()=>({...a,...i.actions,apiHost:r,magicLinkRedirectUri:o}),[a,i,r,o]);return e(re.Provider,{value:s,children:t})},ie=()=>{const{apiHost:e,magicLinkRedirectUri:t}=ne();return d(()=>Y({apiHost:e,magicLinkRedirectUri:t}),[e,t])},ae=["passkey","magic_link"],se=()=>{const e=ie(),[t,r]=g(null),[n,o]=g(!0);return m(()=>{let t=!0;return(async()=>{try{const{data:n,error:o}=await e.getPublicSystemConfig();t&&!o&&n?.loginMethods?.length&&r(n.loginMethods)}catch{}finally{t&&o(!1)}})(),()=>{t=!1}},[e]),{loginMethods:t,loading:n}},le=e=>Boolean(e?.some(e=>"passkey"!==e)),ce=e=>{if("string"!=typeof e)return!1;const t=e.trim();if(t.length<3||t.length>320)return!1;const r=t.split("@");if(2!==r.length)return!1;const[n,o]=r;return!(!/^[A-Za-z0-9._%+-]+$/.test(n)||n.startsWith(".")||n.endsWith(".")||n.includes(".."))&&!(!/^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(o)||o.startsWith("-")||o.endsWith("-")||o.startsWith(".")||o.endsWith(".")||o.includes(".."))},de=e=>{const t=P(e);return t?.isValid()||!1};async function ue(){return async function(){if("undefined"==typeof window)return!1;if(window.PublicKeyCredential&&"function"==typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable)try{return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()}catch{return console.error("Error checking passkey support."),!1}return!1}()}const me=()=>{const[e,t]=g(!1),[r,n]=g(!0);return m(()=>{let e=!0;return(async()=>{try{const r=await ue();e&&t(r)}catch{e&&t(!1)}finally{e&&n(!1)}})(),()=>{e=!1}},[]),{passkeySupported:e,loading:r}},_e="/login",ge="/passkey-login",pe="/verify-phone-otp",fe="/verify-email-otp",he="/verify-magiclink",ye="/oauth/callback",be="/register-passkey",ve="/magic-link-sent";function ke(e,t){void 0===t&&(t={});var r=t.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css","top"===r&&n.firstChild?n.insertBefore(o,n.firstChild):n.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var we="login-module__container___YggDx",xe="login-module__card___Ub-Py",Pe="login-module__heading___neEqb",Se="login-module__inputGroup___P6qgS",Oe="login-module__label___mx5vu",Ne="login-module__input___U07lW",Te="login-module__helperText___kDQ4-",Ce="login-module__error___40zxh",Ie="login-module__button___0kFBs",Ae="login-module__submitHint___qOS5Z",Me="login-module__toggle___1JiG1",Ee="login-module__fallbackCard___HdIk-",ze="login-module__fallbackHeader___-il2v",Fe="login-module__fallbackDescription___ea5EB",Ue="login-module__fallbackActions___AD3ni",Le="login-module__fallbackActionButton___lFgWf",Re="login-module__actionTitle___ycrMp",qe="login-module__actionSubtext___opaIV",Be="login-module__linkButton___5lsoe";ke(".login-module__container___YggDx{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center}.login-module__card___Ub-Py{background-color:var(--seamless-surface,#1f2937);border-radius:.5rem;box-shadow:0 4px 6px var(--seamless-shadow,rgba(0,0,0,.1));max-width:28rem;padding:2rem;width:100%}.login-module__heading___neEqb{color:var(--seamless-text,#fff);font-size:1.5rem;font-weight:700;margin-bottom:1.5rem;text-align:center}.login-module__message___qSejT{color:var(--seamless-success,#68d391);text-align:center}.login-module__inputGroup___P6qgS{margin-bottom:1rem}.login-module__label___mx5vu{color:var(--seamless-text-muted,#d1d5db);display:block}.login-module__input___U07lW{background-color:var(--seamless-surface-raised,#4b5563);border:1px solid var(--seamless-border,#d1d5db);border-radius:.375rem;margin-top:.25rem;padding:.5rem;width:100%}.login-module__input___U07lW:focus{box-shadow:0 0 0 2px var(--seamless-accent,#3b82f6);outline:none}.login-module__helperText___kDQ4-{color:var(--seamless-text-muted,#9ca3af);font-size:.75rem;margin-top:.5rem}.login-module__error___40zxh{color:var(--seamless-danger,#f87171);font-size:.875rem}.login-module__button___0kFBs{background-color:var(--seamless-accent,#2563eb);border-radius:.375rem;color:var(--seamless-accent-contrast,#fff);cursor:pointer;margin-top:1rem;padding:.5rem;transition:background-color .3s,opacity .3s;width:100%}.login-module__button___0kFBs:hover:not(:disabled){background-color:var(--seamless-accent-hover,#1d4ed8)}.login-module__button___0kFBs:disabled{cursor:not-allowed;opacity:.6}.login-module__submitHint___qOS5Z{color:var(--seamless-text-muted,#9ca3af);font-size:.75rem;margin-top:.5rem;text-align:center}.login-module__toggle___1JiG1{background:none;border:none;color:var(--seamless-accent-soft,#60a5fa);cursor:pointer;font-size:.875rem;margin-top:1.5rem;text-align:center;width:100%}.login-module__underline___gs-RS{text-decoration:underline}.login-module__phoneRow___oeNtq{display:grid;gap:.5rem;grid-template-columns:1fr 3fr;margin-top:.25rem}.login-module__phoneInput___2MPQC,.login-module__select___rRV1O{background-color:var(--seamless-surface-raised,#4b5563);border:1px solid var(--seamless-border,#d1d5db);border-radius:.375rem;padding:.5rem;width:100%}@media (max-width:500px){.login-module__phoneRow___oeNtq{grid-template-columns:1fr}}.login-module__fallbackCard___HdIk-{background:var(--seamless-surface-raised,hsla(0,0%,100%,.04));border:1px solid var(--seamless-border,hsla(0,0%,100%,.08));border-radius:12px;display:flex;flex-direction:column;gap:14px;margin-top:18px;padding:18px}.login-module__fallbackHeader___-il2v{color:var(--seamless-text,#f3f4f6);font-size:.95rem;font-weight:600}.login-module__fallbackDescription___ea5EB{color:var(--seamless-text-muted,#9ca3af);font-size:.85rem;line-height:1.4}.login-module__fallbackActions___AD3ni{display:flex;flex-direction:column;gap:10px}.login-module__fallbackActionButton___lFgWf{background:var(--seamless-surface,rgba(0,0,0,.25));border:1px solid var(--seamless-border,hsla(0,0%,100%,.08));border-radius:8px;cursor:pointer;display:flex;flex-direction:column;padding:12px 14px;text-align:left;transition:all .15s ease}.login-module__fallbackActionButton___lFgWf:hover{background:var(--seamless-surface-hover,hsla(0,0%,100%,.06));border-color:var(--seamless-border,hsla(0,0%,100%,.15))}.login-module__actionTitle___ycrMp{color:var(--seamless-text,#f9fafb);font-size:.9rem;font-weight:500}.login-module__actionSubtext___opaIV{color:var(--seamless-text-muted,#9ca3af);font-size:.75rem;margin-top:2px}.login-module__linkButton___5lsoe{background:none;border:none;color:var(--seamless-accent-soft,#60a5fa);cursor:pointer;font-size:.8rem;margin-top:6px}.login-module__linkButton___5lsoe:hover{text-decoration:underline}");const je=({identifier:r,onMagicLink:n,onEmailOtp:o,onPhoneOtp:i,onPasskeyRetry:a,loginMethods:s})=>{const l=new Set(s??["passkey","magic_link","phone_otp"]),c=l.has("magic_link")&&ce(r),d=l.has("email_otp")&&Boolean(o)&&ce(r),u=l.has("phone_otp")&&de(r),m=l.has("passkey")&&Boolean(a);return c||d||u||m?t("div",{className:Ee,children:[e("div",{className:ze,children:"Choose a sign-in method"}),e("p",{className:Fe,children:"Choose another secure sign-in method."}),t("div",{className:Ue,children:[c&&t("button",{type:"button",className:Le,onClick:n,children:[e("span",{className:Re,children:"Email Magic Link"}),e("span",{className:qe,children:"Send a secure sign-in link to your email"})]}),d&&t("button",{type:"button",className:Le,onClick:o,children:[e("span",{className:Re,children:"Email Code"}),e("span",{className:qe,children:"Receive a one-time code by email"})]}),u&&t("button",{type:"button",className:Le,onClick:i,children:[e("span",{className:Re,children:"Text Message Code"}),e("span",{className:qe,children:"Receive a one-time code via SMS"})]})]}),m&&e("button",{type:"button",className:Be,onClick:a,children:"Try passkey anyway"})]}):null},Je="seamless:oauth:provider",Ge=()=>{const{listOAuthProviders:r,startOAuthLogin:n}=ne(),o=h("/oauth/callback"),[i,a]=g([]),[s,l]=g("");if(m(()=>{let e=!0;return r().then(({data:t})=>{e&&a(t?.providers??[])}),()=>{e=!1}},[r]),0===i.length)return null;return t("div",{className:Ue,children:[i.map(r=>e("button",{type:"button",className:Le,onClick:()=>(async e=>{l(""),sessionStorage.setItem(Je,e);const{data:t,error:r}=await n({providerId:e,redirectUri:new URL(o,window.location.origin).toString()});r?l("Could not start sign-in with this provider."):window.location.assign(t.authorizationUrl)})(r.id),children:t("span",{className:Re,children:["Continue with ",r.name]})},r.id)),s&&e("p",{className:Ce,children:s})]})},We=()=>{const n=y(),{hasSignedInBefore:o,login:i,handlePasskeyLogin:a}=ne(),s=ie(),{passkeySupported:l}=me(),{loginMethods:c}=se(),[d,u]=g(""),[_,p]=g(""),[f,h]=g("register"),[b,v]=g(""),[k,w]=g(""),[x,P]=g(""),[S,O]=g(!1),[N,T]=g(null);m(()=>{o&&h("login")},[o]);const C=()=>"login"===f?ce(d)||de(d):ce(_);return e("div",{className:we,children:t("div",{className:xe,children:[e("h2",{className:Pe,children:"login"===f?"Sign In":"Create Account"}),t(r,{children:[t("form",{onSubmit:async e=>{e.preventDefault(),v(""),O(!1);try{if("login"===f){const{data:e,error:t}=await i(d,l);if(t)return void v("Failed to start sign-in. Please try again.");const r=e?.loginMethods?.length?e.loginMethods:c??ae;if(T(r),l&&r.includes("passkey")){const{error:e}=await a();return e?(O(!0),void v("Passkey sign-in could not be completed. Choose another sign-in method.")):void n("/")}return void O(!0)}"register"===f&&await(async()=>{v("");const{data:e,error:t}=await s.register({email:_});t?v("Failed to register. Please try again."):"Success"===e.message?n(fe):v("An unexpected error occurred. Try again. If the problem persists, contact support.")})()}catch{console.error("Failed to continue sign-in."),v("Failed to continue sign-in. Please try again.")}},children:["login"===f&&t("div",{className:Se,children:[e("label",{htmlFor:"identifier",className:Oe,children:"Email Address / Phone Number"}),e("input",{id:"identifier",type:"text",value:d,onChange:e=>{const t=e.target.value;u(t)},autoComplete:"off",placeholder:"Email or Phone Number",className:Ne,onBlur:()=>{if(d){const e=ce(d)||de(d);P(e?"":"Please enter a valid email or phone number")}},required:!0}),e("p",{className:Te,children:"Phone numbers must include a country code e.g. +1"}),S&&e(je,{identifier:d,onMagicLink:async()=>{const{error:e}=await s.requestMagicLink();e?v("Failed to send magic link."):n(ve,{state:{identifier:d}})},onEmailOtp:async()=>{const{error:e}=await s.requestLoginEmailOtp();e?v("Failed to send email code."):n(fe,{state:{flow:"login"}})},onPhoneOtp:async()=>{const{error:e}=await s.requestLoginPhoneOtp();e?v("Failed to send OTP."):n(pe,{state:{flow:"login"}})},onPasskeyRetry:l?a:void 0,loginMethods:N}),x&&e("p",{className:Ce,children:x})]}),"register"===f&&t("div",{className:Se,children:[e("label",{htmlFor:"email",className:Oe,children:"Email Address"}),e("input",{id:"email",type:"email",value:_,onChange:e=>{const t=e.target.value;p(t)},autoComplete:"off",className:Ne,onBlur:()=>{if(_){const e=ce(_);w(e?"":"Please enter a valid email")}},required:!0}),k&&e("p",{className:Ce,children:k})]}),e("button",{type:"submit",className:Ie,disabled:!C(),"aria-describedby":"seamless-submit-hint",children:"login"===f?"Login":"Register"}),e("p",{id:"seamless-submit-hint",role:"status",className:Ae,children:"login"===f?d?C()?"Ready to continue.":"This does not look like a complete email or phone number yet.":"Enter your email or phone number to continue.":_?C()?"Ready to continue.":"This does not look like a complete email address yet.":"Enter your email address to continue."}),b&&e("p",{className:Ce,children:b}),e("button",{type:"button",onClick:()=>h("login"===f?"register":"login"),className:Me,children:"login"===f?"Don't have an account? Create one":"Already have an account? Sign in"})]}),e(Ge,{})]})]})})};var $e={container:"passKeyLogin-module__container___IUKxF",card:"passKeyLogin-module__card___2sKM1",title:"passKeyLogin-module__title___HZxvc",button:"passKeyLogin-module__button___FvR4I"};ke(".passKeyLogin-module__container___IUKxF{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center;padding:1rem}.passKeyLogin-module__card___2sKM1{background-color:var(--seamless-surface,#1f2937);border-radius:1rem;box-shadow:0 4px 20px var(--seamless-shadow,rgba(0,0,0,.4));display:flex;flex-direction:column;gap:1.5rem;max-width:400px;padding:2rem;width:100%}.passKeyLogin-module__title___HZxvc{color:var(--seamless-text,#fff);font-size:1.5rem;font-weight:600;text-align:center}.passKeyLogin-module__button___FvR4I{background-color:var(--seamless-accent,#2563eb);border:1px solid var(--seamless-accent,#3b82f6);border-radius:.5rem;color:var(--seamless-accent-contrast,#fff);cursor:pointer;font-weight:500;padding:.75rem 1rem;text-align:center;transition:background-color .2s ease;width:100%}.passKeyLogin-module__button___FvR4I:hover{background-color:var(--seamless-accent-hover,#1d4ed8)}@media (max-width:480px){.passKeyLogin-module__card___2sKM1{padding:1.25rem}.passKeyLogin-module__button___FvR4I{font-size:1rem}}");const De=()=>{const r=y(),{handlePasskeyLogin:n}=ne(),[o,i]=g("");return e("div",{className:$e.container,children:t("div",{className:$e.card,children:[e("h2",{className:$e.title,children:"Login with Passkey"}),e("button",{onClick:async()=>{i("");const{error:e}=await n();e?i("Passkey sign-in could not be completed. Try another sign-in method."):r("/")},className:$e.button,children:"Use Passkey"}),o&&e("p",{className:$e.error,children:o})]})})};var Ve="registerPasskey-module__container___jqEh8",He="registerPasskey-module__card___MJAyC",Ke="registerPasskey-module__loading___Q4i1x",Ye="registerPasskey-module__spinner___V1nhd",Ze="registerPasskey-module__supported___ihTih",Qe="registerPasskey-module__title___vn2Qu",Xe="registerPasskey-module__description___AWmO-",et="registerPasskey-module__button___D3b4B",tt="registerPasskey-module__message___RiOw-",rt="registerPasskey-module__success___HP5aX",nt="registerPasskey-module__error___hGiqE",ot="registerPasskey-module__skip___1hDyU",it="registerPasskey-module__secondary___bnOB4";ke(".registerPasskey-module__container___jqEh8{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center;padding:1rem}.registerPasskey-module__card___MJAyC{background-color:var(--seamless-surface,#1f2937);border-radius:1rem;box-shadow:0 4px 20px var(--seamless-shadow,rgba(0,0,0,.4));max-width:400px;padding:1.5rem;width:100%}.registerPasskey-module__loading___Q4i1x{align-items:center;background-color:var(--seamless-surface,#f9fafb);border-radius:.75rem;box-shadow:0 2px 8px var(--seamless-shadow,rgba(0,0,0,.1));color:var(--seamless-text,#374151);display:flex;font-weight:500;gap:.75rem;justify-content:center;padding:1.5rem}.registerPasskey-module__spinner___V1nhd{animation:registerPasskey-module__spin___oYcsC 1s linear infinite;border-bottom:2px solid var(--seamless-accent,#3b82f6);border-left:2px solid transparent;border-radius:9999px;border-right:2px solid transparent;border-top:2px solid var(--seamless-accent,#3b82f6);height:1.5rem;width:1.5rem}@keyframes registerPasskey-module__spin___oYcsC{to{transform:rotate(1turn)}}.registerPasskey-module__supported___ihTih{background-color:var(--seamless-surface,#ecfdf5);border-radius:.75rem;box-shadow:0 2px 12px var(--seamless-shadow,rgba(0,0,0,.1));color:var(--seamless-text,#065f46);padding:1.25rem}.registerPasskey-module__title___vn2Qu{font-size:1.125rem;font-weight:600;margin-bottom:.5rem}.registerPasskey-module__description___AWmO-{margin-bottom:1rem}.registerPasskey-module__button___D3b4B{background-color:var(--seamless-accent,#059669);border:none;border-radius:.5rem;color:var(--seamless-accent-contrast,#fff);cursor:pointer;font-weight:500;padding:.75rem 1rem;transition:background-color .2s ease;width:100%}.registerPasskey-module__button___D3b4B:hover{background-color:var(--seamless-accent-hover,#047857)}.registerPasskey-module__message___RiOw-{font-size:.875rem;margin-top:1rem}.registerPasskey-module__success___HP5aX{color:var(--seamless-success,#6ee7b7)}.registerPasskey-module__error___hGiqE{color:var(--seamless-danger,#f87171)}.registerPasskey-module__skip___1hDyU{background:none;border:none;color:var(--seamless-accent-soft,#60a5fa);cursor:pointer;font-size:.875rem;margin-top:1.5rem;text-align:center;width:100%}.registerPasskey-module__skip___1hDyU:disabled{cursor:default;opacity:.6}.registerPasskey-module__secondary___bnOB4{background:none;border:1px solid var(--seamless-accent,#059669);border-radius:.5rem;color:var(--seamless-accent,#059669);cursor:pointer;font-weight:500;margin-top:.75rem;padding:.75rem 1rem;transition:background-color .2s ease;width:100%}.registerPasskey-module__secondary___bnOB4:hover:not(:disabled){background-color:var(--seamless-accent-muted,rgba(5,150,105,.1))}.registerPasskey-module__secondary___bnOB4:disabled{cursor:default;opacity:.6}");const at={attachment_not_allowed:"This application does not accept that kind of authenticator. Try the other option.",synced_passkey_not_allowed:"This passkey syncs to a password manager, and this application requires one that stays on a single device, such as a security key.",authenticator_not_allowed:"This application does not accept this authenticator.",prf_required:"This authenticator does not support a feature this application requires."};const st=()=>{const{refreshSession:r}=ne(),n=ie(),{passkeySupported:o,loading:i}=me(),{loginMethods:a,loading:s}=se(),l=y(),[c,d]=g("idle"),[u,m]=g(""),_=le(a),p=async()=>{await r(),l("/")},f=async e=>{const{platform:t,browser:o,deviceInfo:i}=function(){const e=navigator.userAgent.toLowerCase();let t="unknown",r="unknown";return/iphone|ipad|ipod/.test(e)?t="ios":/android/.test(e)?t="android":/mac os/.test(e)?t="mac":/windows/.test(e)?t="windows":/linux/.test(e)&&(t="linux"),navigator.brave?r="brave":/edg\/|edgios/.test(e)?r="edge":/opr\/|opt\/|opera/.test(e)?r="opera":/vivaldi/.test(e)?r="vivaldi":/samsungbrowser/.test(e)?r="samsung":/firefox|fxios/.test(e)?r="firefox":/chrome|crios/.test(e)?r="chrome":/safari/.test(e)&&(r="safari"),{platform:t,browser:r,deviceInfo:`${t} • ${r}`}}(),a={friendlyName:i,platform:t,browser:o,deviceInfo:i};d("loading");try{const{error:t}=await n.registerPasskey({metadata:a,attachment:e});if(t)throw t;await r(),d("success"),m("Passkey registered successfully."),l("/")}catch(e){console.error("Passkey registration failed."),d("error"),m(function(e){const t=A(e);return t?at[t]:void 0}(e)??(M(e)?"Your session expired before the passkey was saved. Sign in again to add one.":"Error registering passkey."))}};return e("div",{className:Ve,children:e("div",{className:He,children:t("div",i||s?{className:Ke,children:[e("div",{className:Ye}),e("span",{children:"Checking for Passkey Support..."})]}:o?{className:Ze,children:[e("h2",{className:Qe,children:"Secure Your Account with a Passkey"}),e("p",{className:Xe,children:"Your device supports passkeys! Register one to skip passwords forever."}),e("button",{onClick:()=>f(),disabled:"loading"===c,className:et,children:"loading"===c?"Registering...":"Register Passkey"}),e("button",{type:"button",onClick:()=>f("cross-platform"),disabled:"loading"===c,className:it,children:"Use a security key instead"}),u&&e("p",{className:`${tt} ${"success"===c?rt:nt}`,children:u}),_&&e("button",{type:"button",onClick:p,disabled:"loading"===c,className:ot,children:"Skip for now"})]}:{className:Ze,children:[e("h2",{className:Qe,children:"Passkeys are not available here"}),e("p",{className:Xe,children:_?"This device does not support passkeys. You can continue without one and add a passkey later from a device that does.":"This device does not support passkeys, and this application requires one to sign in. Try again from a device or browser that supports them."}),_&&e("button",{type:"button",onClick:p,className:et,children:"Continue"})]})})})};var lt="verifyOTP-module__container___wgoDE",ct="verifyOTP-module__card___3cs2p",dt="verifyOTP-module__title___fZ-1d",ut="verifyOTP-module__subtitle___dGSu3",mt="verifyOTP-module__error___GEtlG",_t="verifyOTP-module__success___46TjG",gt="verifyOTP-module__form___jiGG-",pt="verifyOTP-module__label___Zq5Mi",ft="verifyOTP-module__timer___smq-a",ht="verifyOTP-module__resend___PlmkG",yt="verifyOTP-module__button___8pIuO",bt="verifyOTP-module__toggle___wbzVa";ke(".verifyOTP-module__container___wgoDE{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center;padding:1rem}.verifyOTP-module__card___3cs2p{background-color:var(--seamless-surface,#1f2937);border-radius:.5rem;box-shadow:0 0 10px var(--seamless-shadow,rgba(0,0,0,.3));max-width:28rem;padding:1.5rem;width:100%}.verifyOTP-module__title___fZ-1d{color:var(--seamless-text,#fff);font-size:1.5rem;font-weight:700;margin-bottom:1rem;text-align:center}.verifyOTP-module__subtitle___dGSu3{color:var(--seamless-text-muted,#9ca3af);font-size:.875rem;margin-bottom:1.5rem;text-align:center}.verifyOTP-module__error___GEtlG{color:var(--seamless-danger,#f87171);margin-bottom:1rem;text-align:center}.verifyOTP-module__success___46TjG{color:var(--seamless-success,#34d399);margin-bottom:.75rem;text-align:center}.verifyOTP-module__form___jiGG-{display:flex;flex-direction:column;gap:1rem}.verifyOTP-module__label___Zq5Mi{color:var(--seamless-text-muted,#d1d5db);display:block;font-size:.875rem;margin-bottom:.25rem}.verifyOTP-module__timer___smq-a{color:var(--seamless-warning,#facc15);font-size:.875rem}.verifyOTP-module__input___yqIXM{background-color:var(--seamless-surface-raised,#374151);border:1px solid var(--seamless-border,#4b5563);border-radius:.375rem;color:var(--seamless-text,#fff);outline:none;padding:.5rem;width:100%}.verifyOTP-module__input___yqIXM:focus{box-shadow:0 0 0 2px var(--seamless-accent,#3b82f6)}.verifyOTP-module__resend___PlmkG{background:none;border:none;color:var(--seamless-accent-soft,#60a5fa);cursor:pointer;font-size:.75rem;margin-top:.25rem;text-decoration:underline}.verifyOTP-module__resend___PlmkG:hover{color:var(--seamless-accent,#3b82f6)}.verifyOTP-module__button___8pIuO{background-color:var(--seamless-accent,#2563eb);border:none;border-radius:.375rem;color:var(--seamless-accent-contrast,#fff);cursor:pointer;padding:.5rem;transition:background-color .2s;width:100%}.verifyOTP-module__button___8pIuO:hover{background-color:var(--seamless-accent-hover,#1d4ed8)}.verifyOTP-module__toggle___wbzVa{background:none;border:none;color:var(--seamless-accent-soft,#60a5fa);cursor:pointer;font-size:.875rem;margin-top:1.5rem;text-align:center;width:100%}");var vt="otpInput-module__otpContainer___MUjby",kt="otpInput-module__otpInput___3YMVm";ke(".otpInput-module__otpContainer___MUjby{display:flex;gap:8px;justify-content:center;margin-top:8px}.otpInput-module__otpInput___3YMVm{background:var(--seamless-surface-raised,#374151);border:1px solid var(--seamless-border,#4b5563);border-radius:8px;color:var(--seamless-text,#fff);font-size:1.2rem;height:48px;text-align:center;transition:border-color .15s ease,box-shadow .15s ease;width:42px}.otpInput-module__otpInput___3YMVm:focus{border-color:var(--seamless-accent,#60a5fa);box-shadow:0 0 0 2px var(--seamless-accent,rgba(96,165,250,.25));outline:none}");const wt=({length:t=6,value:r,inputMode:n="numeric",onChange:o,name:i="otp"})=>{const a=p([]),s=r.split("").concat(Array(t).fill("")).slice(0,t),l=(e,t)=>{var r;if(t.length>1)c(e,t);else if(!t||(r=t,"numeric"===n?/^\d$/.test(r):/^[a-zA-Z]$/.test(r))){const r=[...s];r[e]=t;const n=r.join("");o(n),t&&a.current[e+1]&&a.current[e+1]?.focus()}},c=(e,r)=>{let i=r;i="numeric"===n?r.replace(/\D/g,""):r.replace(/[^a-zA-Z]/g,"");const l=i.slice(0,t).split(""),c=[...s];l.forEach((r,n)=>{e+n<t&&(c[e+n]=r)}),o(c.join("")),a.current[Math.min(e+l.length,t-1)]?.focus()};return e("div",{className:vt,role:"group","aria-label":"One-time password input",onPaste:e=>{e.preventDefault(),c(0,e.clipboardData.getData("text"))},children:s.map((r,c)=>e("input",{ref:e=>a.current[c]=e,type:"text",inputMode:"numeric"===n?"numeric":"text",autoComplete:"one-time-code",name:`${i}-${c}`,maxLength:1,value:r,className:kt,onChange:e=>l(c,e.target.value),onKeyDown:e=>((e,r)=>{if("Backspace"===r.key){r.preventDefault();const t=[...s];s[e]?(t[e]="",o(t.join(""))):e>0&&(t[e-1]="",o(t.join("")),a.current[e-1]?.focus())}"ArrowLeft"===r.key&&e>0&&a.current[e-1]?.focus(),"ArrowRight"===r.key&&e<t-1&&a.current[e+1]?.focus()})(c,e),"aria-label":`Digit ${c+1}`},c))})},xt=()=>{const r=y(),n=b(),{refreshSession:o}=ne(),i="login"===n.state?.flow,[a,s]=g(""),[l,c]=g(null),[d,u]=g(""),[_,p]=g(!1),[f,h]=g(""),[v,k]=g(300),w=ie(),x=async()=>{if(p(!0),!l){const{error:e}=i?await w.verifyLoginPhoneOtp(a):await w.verifyPhoneOtp(a);if(e)u("Verification failed.");else{if(i)return await o(),void r("/");c(!0)}}p(!1)},P=async()=>{h(""),await(async()=>{u("");const{error:e}=i?await w.requestLoginPhoneOtp():await w.requestPhoneOtp();e?u("Failed to send SMS code. If this persists, refresh the page and try again."):h("Verification SMS has been resent.")})(),h("Verification SMS has been resent.")};return m(()=>{const e=setInterval(()=>{k(e=>e>0?e-1:0)},1e3);return()=>clearInterval(e)},[]),m(()=>{l&&!i&&(async()=>{const{error:e}=await w.requestEmailOtp();e?u("Failed to send Email code. If this persists, refresh the page and try registering again."):r(fe)})()},[l,i,r,w]),e("div",{className:lt,children:t("div",{className:ct,children:[e("h2",{className:dt,children:"Verify Your Phone Number"}),e("p",{className:ut,children:"Enter the code sent to your phone number."}),d&&e("p",{className:mt,children:d}),f&&e("p",{className:_t,children:f}),t("form",{onSubmit:async e=>{if(e.preventDefault(),u(""),6===a.length){p(!0);try{await x()}catch{u("Unexpected error occurred.")}finally{p(!1)}}else u("Please enter a valid code.")},className:gt,children:[t("div",{children:[t("label",{htmlFor:"phoneCode",className:pt,children:["Phone Verification Code ",(O=l,!0===O?e("span",{className:"text-green-400 ml-2",children:"✅"}):!1===O?e("span",{className:"text-red-400 ml-2",children:"❌"}):null)," -"," ",t("span",{className:ft,children:["Code expires in: ",(S=v,`${Math.floor(S/60).toString().padStart(2,"0")}:${(S%60).toString().padStart(2,"0")}`)]})]}),e(wt,{length:6,value:a,onChange:s}),e("button",{type:"button",onClick:()=>P(),className:ht,children:"Resend code to phone"})]}),e("button",{type:"submit",className:yt,children:_?"Verifying...":"Verify & Continue"}),e("button",{type:"button",onClick:()=>r("/login"),className:bt,children:"Back to login"})]})]})});var S,O},Pt=()=>{const r=y(),n=b(),{refreshSession:o}=ne(),i=ie(),{passkeySupported:a}=me(),s="login"===n.state?.flow,[l,c]=g(!1),[d,u]=g(""),[_,p]=g(300),[f,h]=g(""),[v,k]=g("");return m(()=>{const e=setInterval(()=>{p(e=>e>0?e-1:0)},1e3);return()=>clearInterval(e)},[]),e("div",{className:lt,children:t("div",{className:ct,children:[e("h2",{className:dt,children:"Verify Your Email"}),e("p",{className:ut,children:"We sent you a verification email. Enter the code below."}),f&&e("p",{className:mt,children:f}),v&&e("p",{className:_t,children:v}),t("form",{onSubmit:async e=>{if(e.preventDefault(),h(""),6===d.length){c(!0);try{const{error:e}=s?await i.verifyLoginEmailOtp(d):await i.verifyEmailOtp(d);if(e)return void h("Verification failed.");if(s)return await o(),void r("/");a?r(be):(await o(),r("/"))}catch{console.error("Email OTP verification failed."),h("Verification failed.")}finally{c(!1)}}else h("Please enter a valid code.")},className:gt,children:[t("div",{children:[t("label",{htmlFor:"emailCode",className:pt,children:["Email Verification Code",t("span",{className:ft,children:[" ","— Code expires in ",(w=_,`${Math.floor(w/60).toString().padStart(2,"0")}:${(w%60).toString().padStart(2,"0")}`)]})]}),e(wt,{length:6,value:d,onChange:u,inputMode:"text"}),e("button",{type:"button",onClick:async()=>{h(""),k("");const{error:e}=s?await i.requestLoginEmailOtp():await i.requestEmailOtp();e?h("Failed to send Email code. If this persists, refresh the page and try again."):k("Verification email has been resent.")},className:ht,children:"Resend code to email"})]}),e("button",{type:"submit",className:yt,disabled:l,children:l?"Verifying...":"Verify & Continue"}),e("button",{type:"button",onClick:()=>r("/login"),className:bt,children:"Back to login"})]})]})});var w};var St={container:"verifyMagiclink-module__container___qrSXf",card:"verifyMagiclink-module__card___niKgr",title:"verifyMagiclink-module__title___SumxD",verificationContent:"verifyMagiclink-module__verificationContent___NdsS0",spinner:"verifyMagiclink-module__spinner___lnW1k",successIcon:"verifyMagiclink-module__successIcon___XjFEM",checkIcon:"verifyMagiclink-module__checkIcon___I0JOP",successText:"verifyMagiclink-module__successText___lO5Qq"};ke(".verifyMagiclink-module__container___qrSXf{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center;padding:1rem}.verifyMagiclink-module__card___niKgr{background-color:var(--seamless-surface,#1f2937);border-radius:.5rem;box-shadow:0 0 10px var(--seamless-shadow,rgba(0,0,0,.3));max-width:28rem;padding:1.5rem;width:100%}.verifyMagiclink-module__title___SumxD{color:var(--seamless-text,#fff);font-size:1.5rem;font-weight:700;margin-bottom:1rem;text-align:center}.verifyMagiclink-module__verificationContent___NdsS0{align-items:center;display:flex;flex-direction:column;gap:14px;margin:20px 0}.verifyMagiclink-module__spinner___lnW1k{animation:verifyMagiclink-module__spin___2lGzj .9s linear infinite;border:3px solid var(--seamless-border,hsla(0,0%,100%,.15));border-radius:50%;border-top-color:var(--seamless-accent,#60a5fa);height:28px;width:28px}.verifyMagiclink-module__successIcon___XjFEM{align-items:center;background:rgba(16,185,129,.15);border-radius:50%;display:flex;height:36px;justify-content:center;width:36px}.verifyMagiclink-module__checkIcon___I0JOP{animation:verifyMagiclink-module__checkPop___-3qJu .25s ease-out;color:var(--seamless-success,#34d399);height:22px;width:22px}.verifyMagiclink-module__successText___lO5Qq{color:var(--seamless-success,#34d399);font-size:.9rem}@keyframes verifyMagiclink-module__checkPop___-3qJu{0%{opacity:0;transform:scale(.6)}80%{transform:scale(1.15)}to{opacity:1;transform:scale(1)}}@keyframes verifyMagiclink-module__spin___2lGzj{to{transform:rotate(1turn)}}");const Ot=()=>{const[r]=v(),n=y(),o=r.get("token"),[i,a]=g(""),[s,l]=g(""),c=ie(),{refreshSession:d}=ne();return m(()=>{let e=!0,t=null,r=null;return(async()=>{if(!o)return e&&a("Missing token for verification."),void console.error("No magic-link token found.");const{error:i}=await c.verifyMagicLink(o);if(i)return console.error("Failed to verify token"),void(e&&a("Failed to verify token"));await d(),e&&(t=new BroadcastChannel("seamless-auth"),t.postMessage({type:"MAGIC_LINK_AUTH_SUCCESS"}),l("You have been verified on the device and browser that initiated this request"),r=setTimeout(()=>{e&&n("/")},900))})(),()=>{e=!1,t?.close(),r&&clearTimeout(r)}},[o,c,n,d]),e("div",{className:St.container,children:t("div",{className:St.card,children:[e("h1",{className:St.title,children:"Verifying your login"}),t("div",{className:St.verificationContent,children:[!s&&!i&&e("div",{className:St.spinner}),s&&e("div",{className:St.successIcon,children:e("svg",{viewBox:"0 0 24 24",className:St.checkIcon,fill:"none",stroke:"currentColor",strokeWidth:"2",children:e("path",{d:"M5 13l4 4L19 7"})})}),!s&&!i&&e("p",{className:St.helperText,children:"Please wait while we securely verify your sign-in link."}),s&&e("p",{className:St.successText,children:"Login verified. Redirecting…"}),i&&e("p",{className:St.error,children:i})]})]})})};const Nt={oauth_missing_email:"Your provider account did not share an email address. Add an email to that account and make it visible, then try again.",oauth_email_not_verified:"The email address on your provider account is not verified. Verify it with your provider, then try again.",oauth_missing_subject:"Your provider did not return a usable account identifier. Try again, or sign in with a different method."},Tt=()=>{const{finishOAuthLogin:n}=ne(),o=y(),[i]=v(),[a,s]=g(""),l=p(!1);return m(()=>{if(l.current)return;l.current=!0;const e=i.get("code"),t=i.get("state"),r=sessionStorage.getItem(Je);e&&t&&r?n({providerId:r,code:e,state:t}).then(({data:e,error:t})=>{if(t){const e=T(t);return void s(e?Nt[e]:"We could not complete sign-in. Please try again.")}sessionStorage.removeItem(Je),o(function(e){if(!e)return null;try{const t=new URL(e,window.location.origin);return t.origin!==window.location.origin?null:`${t.pathname}${t.search}${t.hash}`}catch{return null}}(e?.returnTo)??"/")}):s("This sign-in link is missing required information.")},[n,o,i]),t("div",{className:St.container,children:[e("h2",{className:St.title,children:a?"Sign-in failed":"Completing sign-in..."}),a&&t(r,{children:[e("p",{children:a}),e("button",{type:"button",onClick:()=>o("/login"),children:"Back to login"})]})]})};var Ct="magiclink-module__container___45zcA",It="magiclink-module__card___Au9oN",At="magiclink-module__iconWrapper___k5ndr",Mt="magiclink-module__mailIcon___Pwb7q",Et="magiclink-module__iconRing___bdLvf",zt="magiclink-module__heading___b9cre",Ft="magiclink-module__message___kiVFj",Ut="magiclink-module__identifier___JtcFM",Lt="magiclink-module__helperText___7Mlfh",Rt="magiclink-module__actions___64I-3",qt="magiclink-module__secondaryButton___BntAI",Bt="magiclink-module__cooldown___Ib4rm",jt="magiclink-module__linkButton___maJiv";ke(".magiclink-module__container___45zcA{align-items:center;color:var(--seamless-text,#fff);display:flex;justify-content:center}.magiclink-module__card___Au9oN{background-color:var(--seamless-surface,#1f2937);border-radius:.75rem;box-shadow:0 10px 25px var(--seamless-shadow,rgba(0,0,0,.25));max-width:420px;padding:2.5rem 2rem;text-align:center;width:100%}.magiclink-module__iconWrapper___k5ndr{display:flex;justify-content:center;margin-bottom:1.25rem;position:relative}.magiclink-module__mailIcon___Pwb7q{color:var(--seamless-accent-soft,#a5b4fc);height:38px;width:38px;z-index:2}.magiclink-module__iconRing___bdLvf{animation:magiclink-module__pulse___YGm-I 2s infinite;background:rgba(99,102,241,.15);border-radius:50%;height:72px;position:absolute;width:72px}@keyframes magiclink-module__pulse___YGm-I{0%{opacity:.8;transform:scale(.6)}70%{opacity:0;transform:scale(1.4)}to{opacity:0}}.magiclink-module__heading___b9cre{font-size:1.55rem;font-weight:600;margin-bottom:.75rem}.magiclink-module__message___kiVFj{color:var(--seamless-text-muted,#d1d5db);font-size:.95rem;line-height:1.4;margin-bottom:1rem}.magiclink-module__identifier___JtcFM{background:var(--seamless-surface-raised,hsla(0,0%,100%,.05));border-radius:6px;display:inline-block;font-size:1rem;font-weight:600;margin-bottom:1rem;padding:.45rem .6rem}.magiclink-module__helperText___7Mlfh{color:var(--seamless-text-muted,#9ca3af);font-size:.85rem;line-height:1.4;margin-bottom:.6rem}.magiclink-module__actions___64I-3{display:flex;flex-direction:column;gap:.6rem;margin-top:1.6rem}.magiclink-module__secondaryButton___BntAI{background:var(--seamless-surface,#111827);border:1px solid var(--seamless-border,#374151);border-radius:8px;color:var(--seamless-text,#fff);cursor:pointer;font-weight:500;padding:.65rem}.magiclink-module__secondaryButton___BntAI:hover:not(:disabled){background:var(--seamless-surface-hover,#1f2937)}.magiclink-module__secondaryButton___BntAI:disabled{cursor:not-allowed;opacity:.5}.magiclink-module__cooldown___Ib4rm{color:var(--seamless-text-muted,#9ca3af);font-size:.8rem}.magiclink-module__linkButton___maJiv{background:none;border:none;color:var(--seamless-accent-soft,#a5b4fc);cursor:pointer;font-size:.85rem;margin-top:.4rem}.magiclink-module__linkButton___maJiv:hover{text-decoration:underline}");const Jt=()=>{const{refreshSession:r}=ne(),n=y(),o=b(),i=ie(),a=o.state?.identifier,[s,l]=g(30);m(()=>{const e=setInterval(()=>{l(e=>e>0?e-1:0)},1e3);return()=>clearInterval(e)},[]);const c=f(async()=>{const{data:e,error:t}=await i.checkMagicLink();return!t&&"Success"===e?.message},[i]);return m(()=>{const e=new BroadcastChannel("seamless-auth");return e.onmessage=async e=>{"MAGIC_LINK_AUTH_SUCCESS"===e.data?.type&&await c()&&(await r(),n("/"))},()=>{e.close()}},[c,n,r]),m(()=>{const e=setInterval(async()=>{try{await c()&&(await r(),n("/"))}catch{}},5e3);return()=>clearInterval(e)},[c,r,n]),e("div",{className:Ct,children:t("div",{className:It,children:[t("div",{className:At,children:[e("div",{className:Et}),t("svg",{className:Mt,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",children:[e("path",{d:"M4 6h16v12H4z"}),e("path",{d:"m4 6 8 6 8-6"})]})]}),e("h2",{className:zt,children:"Check your email"}),e("p",{className:Ft,children:"If an account exists for this address, we sent a secure sign-in link."}),a&&e("div",{className:Ut,children:a}),e("p",{className:Lt,children:"Open the email and click the link to finish signing in."}),e("p",{className:Lt,children:"Didn’t receive anything? Check your spam folder or try creating a new account."}),t("div",{className:Rt,children:[e("button",{className:qt,disabled:s>0,onClick:async()=>{s>0||(await i.requestMagicLink(),l(30))},children:"Resend link"}),s>0&&t("div",{className:Bt,children:["Available in ",s,"s"]}),e("button",{className:jt,onClick:()=>n("/login"),children:"Change email or phone"})]})]})})},Gt=()=>t(k,{children:[e(w,{path:_e,element:e(We,{})}),e(w,{path:ge,element:e(De,{})}),e(w,{path:pe,element:e(xt,{})}),e(w,{path:fe,element:e(Pt,{})}),e(w,{path:he,element:e(Ot,{})}),e(w,{path:ye,element:e(Tt,{})}),e(w,{path:be,element:e(st,{})}),e(w,{path:ve,element:e(Jt,{})}),e(w,{path:"*",element:e(x,{to:_e,replace:!0})})]});export{oe as AuthProvider,Gt as AuthRoutes,S as SeamlessAuthError,Y as createSeamlessAuthClient,B as encodePrfSalt,$ as extractPasskeyPrfResult,T as getOAuthErrorCode,A as getPasskeyPolicyErrorCode,E as getWebAuthnErrorDetail,le as hasNonPasskeyLoginMethod,Q as hasScopedRole,G as isPasskeyPrfSupported,M as isUnauthenticated,Z as roleGrantsAccess,ne as useAuth,ie as useAuthClient,se as useLoginMethods,me as usePasskeySupport};
|
|
1
|
+
'use client';
|
|
2
|
+
import{u as e}from"./chunks/shared-BzrBuR0z.js";export{A as AuthProvider,h as hasNonPasskeyLoginMethod,a as useAuthClient,b as useLoginMethods,c as usePasskeySupport}from"./chunks/shared-BzrBuR0z.js";export{SeamlessAuthError,createSeamlessAuthClient,encodePrfSalt,extractPasskeyPrfResult,getOAuthErrorCode,getPasskeyPolicyErrorCode,getWebAuthnErrorDetail,hasScopedRole,isPasskeyPrfSupported,isUnauthenticated,roleGrantsAccess}from"@seamless-auth/client";import"react";import"react/jsx-runtime";const s=()=>e().client.authorizedFetch;export{e as useAuth,s as useAuthorizedFetch};
|
|
2
3
|
//# sourceMappingURL=index.js.map
|