@ttoss/react-auth-strapi 0.7.0 → 0.8.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/README.md +97 -0
- package/dist/index.cjs +287 -138
- package/dist/index.d.cts +18 -2
- package/dist/index.d.mts +18 -2
- package/dist/index.mjs +288 -140
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ function AuthenticatedApp() {
|
|
|
51
51
|
|
|
52
52
|
- **Zero configuration**: Pre-configured Strapi authentication handlers
|
|
53
53
|
- **Complete auth flows**: Sign in, sign up, forgot password, email confirmation
|
|
54
|
+
- **Social sign-in**: Google/Facebook via Strapi's Users & Permissions providers
|
|
54
55
|
- **Token management**: Automatic refresh token handling with secure storage
|
|
55
56
|
- **Error handling**: Built-in notifications for authentication errors
|
|
56
57
|
- **Email verification**: Automatic resend confirmation emails for unverified accounts
|
|
@@ -88,6 +89,8 @@ flowchart LR
|
|
|
88
89
|
- `POST /auth/send-email-confirmation` - Resend email confirmation
|
|
89
90
|
- `POST /auth/local/refresh` - Refresh access token
|
|
90
91
|
- `GET /users/me` - Get current user profile
|
|
92
|
+
- `GET /connect/:provider` - Kicks off a Users & Permissions social provider (e.g. Google)
|
|
93
|
+
- `GET /auth/:provider/callback` - Exchanges the provider's callback query string for a Strapi JWT
|
|
91
94
|
|
|
92
95
|
## API Reference
|
|
93
96
|
|
|
@@ -135,6 +138,7 @@ The component automatically handles:
|
|
|
135
138
|
sideContent: <BrandingContent />,
|
|
136
139
|
sideContentPosition: 'left',
|
|
137
140
|
}}
|
|
141
|
+
socialProviders={['Google']} // Optional: renders social sign-in buttons
|
|
138
142
|
/>
|
|
139
143
|
```
|
|
140
144
|
|
|
@@ -289,6 +293,99 @@ sequenceDiagram
|
|
|
289
293
|
A-->>U: Display Sign In screen with success notification
|
|
290
294
|
```
|
|
291
295
|
|
|
296
|
+
## Social Sign-In (Google, Facebook)
|
|
297
|
+
|
|
298
|
+
### Strapi Provider Setup
|
|
299
|
+
|
|
300
|
+
In Strapi Admin → **Settings → Users & Permissions → Providers**, enable the provider (e.g. Google), set its Client ID/Secret, and set **"The redirect URL to your front-end app"** to the route this package's callback handler is mounted at, e.g.:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
https://your-app.com/connect/google/redirect
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Enabling the Button
|
|
307
|
+
|
|
308
|
+
Pass `socialProviders` to `Auth` to render the "Continue with Google" button on the sign-in/sign-up screens. Clicking it redirects the browser to `${apiUrl}/connect/google`, which Strapi uses to start the OAuth flow:
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
import { Auth } from '@ttoss/react-auth-strapi';
|
|
312
|
+
|
|
313
|
+
function LoginPage() {
|
|
314
|
+
return <Auth socialProviders={['Google']} />;
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Handling the Redirect
|
|
319
|
+
|
|
320
|
+
Strapi completes the OAuth exchange itself and redirects the browser back to the URL configured in the admin panel, appending a callback query string (e.g. `?access_token=...`). Mount `AuthSocialSignInCallback` at that route to forward the query string to `${apiUrl}/auth/google/callback` and authenticate the user:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
import { useNavigate } from 'react-router-dom';
|
|
324
|
+
import { AuthSocialSignInCallback } from '@ttoss/react-auth-strapi';
|
|
325
|
+
|
|
326
|
+
function GoogleRedirectPage() {
|
|
327
|
+
const navigate = useNavigate();
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<AuthSocialSignInCallback
|
|
331
|
+
provider="google"
|
|
332
|
+
onSuccess={() => {
|
|
333
|
+
return navigate('/');
|
|
334
|
+
}}
|
|
335
|
+
onError={() => {
|
|
336
|
+
return navigate('/auth');
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
<p>Signing you in…</p>
|
|
340
|
+
</AuthSocialSignInCallback>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
347
|
+
import { AuthProvider } from '@ttoss/react-auth-strapi';
|
|
348
|
+
|
|
349
|
+
function App() {
|
|
350
|
+
return (
|
|
351
|
+
<AuthProvider apiUrl="https://your-strapi-api.com/api">
|
|
352
|
+
<BrowserRouter>
|
|
353
|
+
<Routes>
|
|
354
|
+
<Route path="/auth" element={<AuthPage />} />
|
|
355
|
+
<Route
|
|
356
|
+
path="/connect/google/redirect"
|
|
357
|
+
element={<GoogleRedirectPage />}
|
|
358
|
+
/>
|
|
359
|
+
<Route path="/" element={<HomePage />} />
|
|
360
|
+
</Routes>
|
|
361
|
+
</BrowserRouter>
|
|
362
|
+
</AuthProvider>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Flow
|
|
368
|
+
|
|
369
|
+
```mermaid
|
|
370
|
+
sequenceDiagram
|
|
371
|
+
participant U as User
|
|
372
|
+
participant A as Auth Component
|
|
373
|
+
participant S as Strapi API
|
|
374
|
+
participant G as Google
|
|
375
|
+
participant C as AuthSocialSignInCallback
|
|
376
|
+
|
|
377
|
+
U->>A: Click "Continue with Google"
|
|
378
|
+
A->>S: Redirect to /connect/google
|
|
379
|
+
S->>G: Redirect to Google OAuth consent
|
|
380
|
+
G-->>S: Redirect back with provider code
|
|
381
|
+
S-->>U: Redirect to /connect/google/redirect?access_token=...
|
|
382
|
+
U->>C: Browser loads redirect route
|
|
383
|
+
C->>S: GET /auth/google/callback?access_token=...
|
|
384
|
+
S-->>C: { jwt, refreshToken, user }
|
|
385
|
+
C->>C: setAuthData({ user, tokens, isAuthenticated: true })
|
|
386
|
+
C-->>U: onSuccess() navigates into the app
|
|
387
|
+
```
|
|
388
|
+
|
|
292
389
|
## Error Handling
|
|
293
390
|
|
|
294
391
|
The package integrates with `@ttoss/react-notifications` to display authentication errors:
|
package/dist/index.cjs
CHANGED
|
@@ -149,6 +149,148 @@ var useAuth = () => {
|
|
|
149
149
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/Auth.tsx
|
|
152
|
+
var GENERIC_ERROR_MESSAGE$1 = "Unable to connect to the server. Please check your connection.";
|
|
153
|
+
var signInWithEmail = async ({
|
|
154
|
+
apiUrl,
|
|
155
|
+
email,
|
|
156
|
+
password,
|
|
157
|
+
notifyError,
|
|
158
|
+
onUnconfirmedEmail
|
|
159
|
+
}) => {
|
|
160
|
+
const response = await fetch(`${apiUrl}/auth/local`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json"
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
identifier: email,
|
|
167
|
+
password
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
const data = await response.json();
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
if (data.error?.message === "Your account email is not confirmed") {
|
|
173
|
+
await onUnconfirmedEmail();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
notifyError({
|
|
177
|
+
title: "Sign in failed",
|
|
178
|
+
message: data.error?.message || "An error occurred during sign in."
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
storage.setRefreshToken(data.refreshToken);
|
|
183
|
+
return {
|
|
184
|
+
user: {
|
|
185
|
+
id: data.user.id,
|
|
186
|
+
email: data.user.email,
|
|
187
|
+
emailVerified: data.user.confirmed
|
|
188
|
+
},
|
|
189
|
+
tokens: {
|
|
190
|
+
accessToken: data.jwt,
|
|
191
|
+
refreshToken: data.refreshToken
|
|
192
|
+
},
|
|
193
|
+
isAuthenticated: true
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
var resendEmailConfirmation = async ({
|
|
197
|
+
apiUrl,
|
|
198
|
+
email,
|
|
199
|
+
notifyError
|
|
200
|
+
}) => {
|
|
201
|
+
const response = await fetch(`${apiUrl}/auth/send-email-confirmation`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: {
|
|
204
|
+
"Content-Type": "application/json"
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
email
|
|
208
|
+
})
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
notifyError({
|
|
212
|
+
title: "Resend confirmation email failed",
|
|
213
|
+
message: (await response.json()).error?.message || "An error occurred while resending the confirmation email."
|
|
214
|
+
});
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
};
|
|
219
|
+
var registerWithEmail = async ({
|
|
220
|
+
apiUrl,
|
|
221
|
+
email,
|
|
222
|
+
password,
|
|
223
|
+
notifyError
|
|
224
|
+
}) => {
|
|
225
|
+
const response = await fetch(`${apiUrl}/auth/local/register`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: {
|
|
228
|
+
"Content-Type": "application/json"
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
username: email,
|
|
232
|
+
email,
|
|
233
|
+
password
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
notifyError({
|
|
238
|
+
title: "Sign up failed",
|
|
239
|
+
message: (await response.json()).error?.message || "An error occurred during sign up."
|
|
240
|
+
});
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
};
|
|
245
|
+
var requestPasswordReset = async ({
|
|
246
|
+
apiUrl,
|
|
247
|
+
email,
|
|
248
|
+
notifyError
|
|
249
|
+
}) => {
|
|
250
|
+
const response = await fetch(`${apiUrl}/auth/forgot-password`, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: {
|
|
253
|
+
"Content-Type": "application/json"
|
|
254
|
+
},
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
email
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
notifyError({
|
|
261
|
+
title: "Forgot password failed",
|
|
262
|
+
message: (await response.json()).error?.message || "An error occurred during forgot password."
|
|
263
|
+
});
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
};
|
|
268
|
+
var resetPasswordWithCode = async ({
|
|
269
|
+
apiUrl,
|
|
270
|
+
code,
|
|
271
|
+
newPassword,
|
|
272
|
+
notifyError
|
|
273
|
+
}) => {
|
|
274
|
+
const response = await fetch(`${apiUrl}/auth/reset-password`, {
|
|
275
|
+
method: "POST",
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": "application/json"
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify({
|
|
280
|
+
code,
|
|
281
|
+
password: newPassword,
|
|
282
|
+
passwordConfirmation: newPassword
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
notifyError({
|
|
287
|
+
title: "Reset password failed",
|
|
288
|
+
message: (await response.json()).error?.message || "An error occurred during password reset."
|
|
289
|
+
});
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
};
|
|
152
294
|
var Auth = props => {
|
|
153
295
|
const {
|
|
154
296
|
setAuthData,
|
|
@@ -161,193 +303,124 @@ var Auth = props => {
|
|
|
161
303
|
const {
|
|
162
304
|
addNotification
|
|
163
305
|
} = (0, _ttoss_react_notifications.useNotifications)();
|
|
306
|
+
const notifyError = react.useCallback(({
|
|
307
|
+
title,
|
|
308
|
+
message
|
|
309
|
+
}) => {
|
|
310
|
+
addNotification({
|
|
311
|
+
title,
|
|
312
|
+
message: message || GENERIC_ERROR_MESSAGE$1,
|
|
313
|
+
type: "error"
|
|
314
|
+
});
|
|
315
|
+
}, [addNotification]);
|
|
164
316
|
const onSignIn = react.useCallback(async ({
|
|
165
317
|
email,
|
|
166
318
|
password
|
|
167
319
|
}) => {
|
|
168
320
|
try {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (!response.ok) {
|
|
181
|
-
if (data.error?.message === "Your account email is not confirmed") {
|
|
182
|
-
const resendResponse = await fetch(`${apiUrl}/auth/send-email-confirmation`, {
|
|
183
|
-
method: "POST",
|
|
184
|
-
headers: {
|
|
185
|
-
"Content-Type": "application/json"
|
|
186
|
-
},
|
|
187
|
-
body: JSON.stringify({
|
|
188
|
-
email
|
|
189
|
-
})
|
|
190
|
-
});
|
|
191
|
-
if (!resendResponse.ok) {
|
|
192
|
-
addNotification({
|
|
193
|
-
title: "Resend confirmation email failed",
|
|
194
|
-
message: (await resendResponse.json()).error?.message || "An error occurred while resending the confirmation email.",
|
|
195
|
-
type: "error"
|
|
196
|
-
});
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
setScreen({
|
|
321
|
+
const authData = await signInWithEmail({
|
|
322
|
+
apiUrl,
|
|
323
|
+
email,
|
|
324
|
+
password,
|
|
325
|
+
notifyError,
|
|
326
|
+
onUnconfirmedEmail: async () => {
|
|
327
|
+
if (await resendEmailConfirmation({
|
|
328
|
+
apiUrl,
|
|
329
|
+
email,
|
|
330
|
+
notifyError
|
|
331
|
+
})) setScreen({
|
|
200
332
|
value: "confirmSignUpCheckEmail"
|
|
201
333
|
});
|
|
202
|
-
return;
|
|
203
334
|
}
|
|
204
|
-
addNotification({
|
|
205
|
-
title: "Sign in failed",
|
|
206
|
-
message: data.error?.message || "An error occurred during sign in.",
|
|
207
|
-
type: "error"
|
|
208
|
-
});
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
storage.setRefreshToken(data.refreshToken);
|
|
212
|
-
setAuthData({
|
|
213
|
-
user: {
|
|
214
|
-
id: data.user.id,
|
|
215
|
-
email: data.user.email,
|
|
216
|
-
emailVerified: data.user.confirmed
|
|
217
|
-
},
|
|
218
|
-
tokens: {
|
|
219
|
-
accessToken: data.jwt,
|
|
220
|
-
refreshToken: data.refreshToken
|
|
221
|
-
},
|
|
222
|
-
isAuthenticated: true
|
|
223
335
|
});
|
|
336
|
+
if (authData) setAuthData(authData);
|
|
224
337
|
} catch {
|
|
225
|
-
|
|
226
|
-
title: "Network Error"
|
|
227
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
228
|
-
type: "error"
|
|
338
|
+
notifyError({
|
|
339
|
+
title: "Network Error"
|
|
229
340
|
});
|
|
230
341
|
}
|
|
231
|
-
}, [setAuthData, setScreen,
|
|
342
|
+
}, [setAuthData, setScreen, notifyError, apiUrl]);
|
|
232
343
|
const onSignUp = react.useCallback(async ({
|
|
233
344
|
email,
|
|
234
345
|
password
|
|
235
346
|
}) => {
|
|
236
347
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
username: email,
|
|
244
|
-
email,
|
|
245
|
-
password
|
|
246
|
-
})
|
|
247
|
-
});
|
|
248
|
-
const data = await response.json();
|
|
249
|
-
if (!response.ok) {
|
|
250
|
-
addNotification({
|
|
251
|
-
title: "Sign up failed",
|
|
252
|
-
message: data.error?.message || "An error occurred during sign up.",
|
|
253
|
-
type: "error"
|
|
254
|
-
});
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
setScreen({
|
|
348
|
+
if (await registerWithEmail({
|
|
349
|
+
apiUrl,
|
|
350
|
+
email,
|
|
351
|
+
password,
|
|
352
|
+
notifyError
|
|
353
|
+
})) setScreen({
|
|
258
354
|
value: "confirmSignUpCheckEmail"
|
|
259
355
|
});
|
|
260
356
|
} catch {
|
|
261
|
-
|
|
262
|
-
title: "Network Error"
|
|
263
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
264
|
-
type: "error"
|
|
357
|
+
notifyError({
|
|
358
|
+
title: "Network Error"
|
|
265
359
|
});
|
|
266
360
|
}
|
|
267
|
-
}, [
|
|
361
|
+
}, [setScreen, notifyError, apiUrl]);
|
|
268
362
|
const onForgotPassword = react.useCallback(async ({
|
|
269
363
|
email
|
|
270
364
|
}) => {
|
|
271
365
|
try {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
body: JSON.stringify({
|
|
278
|
-
email
|
|
279
|
-
})
|
|
280
|
-
});
|
|
281
|
-
const data = await response.json();
|
|
282
|
-
if (!response.ok) {
|
|
283
|
-
addNotification({
|
|
284
|
-
title: "Forgot password failed",
|
|
285
|
-
message: data.error?.message || "An error occurred during forgot password.",
|
|
286
|
-
type: "error"
|
|
287
|
-
});
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
setScreen({
|
|
366
|
+
if (await requestPasswordReset({
|
|
367
|
+
apiUrl,
|
|
368
|
+
email,
|
|
369
|
+
notifyError
|
|
370
|
+
})) setScreen({
|
|
291
371
|
value: "confirmResetPassword",
|
|
292
372
|
context: {
|
|
293
373
|
email
|
|
294
374
|
}
|
|
295
375
|
});
|
|
296
376
|
} catch {
|
|
297
|
-
|
|
298
|
-
title: "Network Error"
|
|
299
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
300
|
-
type: "error"
|
|
377
|
+
notifyError({
|
|
378
|
+
title: "Network Error"
|
|
301
379
|
});
|
|
302
380
|
}
|
|
303
|
-
}, [
|
|
381
|
+
}, [setScreen, notifyError, apiUrl]);
|
|
304
382
|
const onForgotPasswordResetPassword = react.useCallback(async ({
|
|
305
|
-
email: _email,
|
|
306
383
|
code,
|
|
307
384
|
newPassword
|
|
308
385
|
}) => {
|
|
309
386
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
code,
|
|
317
|
-
password: newPassword,
|
|
318
|
-
passwordConfirmation: newPassword
|
|
319
|
-
})
|
|
320
|
-
});
|
|
321
|
-
const data = await response.json();
|
|
322
|
-
if (!response.ok) {
|
|
387
|
+
if (await resetPasswordWithCode({
|
|
388
|
+
apiUrl,
|
|
389
|
+
code,
|
|
390
|
+
newPassword,
|
|
391
|
+
notifyError
|
|
392
|
+
})) {
|
|
323
393
|
addNotification({
|
|
324
|
-
title: "
|
|
325
|
-
message:
|
|
326
|
-
type: "
|
|
394
|
+
title: "Password reset successful",
|
|
395
|
+
message: "You can now sign in with your new password.",
|
|
396
|
+
type: "success"
|
|
397
|
+
});
|
|
398
|
+
setScreen({
|
|
399
|
+
value: "signIn"
|
|
327
400
|
});
|
|
328
|
-
return;
|
|
329
401
|
}
|
|
330
|
-
addNotification({
|
|
331
|
-
title: "Password reset successful",
|
|
332
|
-
message: "You can now sign in with your new password.",
|
|
333
|
-
type: "success"
|
|
334
|
-
});
|
|
335
|
-
setScreen({
|
|
336
|
-
value: "signIn"
|
|
337
|
-
});
|
|
338
402
|
} catch {
|
|
339
|
-
|
|
340
|
-
title: "Network Error"
|
|
341
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
342
|
-
type: "error"
|
|
403
|
+
notifyError({
|
|
404
|
+
title: "Network Error"
|
|
343
405
|
});
|
|
344
406
|
}
|
|
345
|
-
}, [
|
|
407
|
+
}, [setScreen, notifyError, addNotification, apiUrl]);
|
|
346
408
|
const onConfirmSignUpCheckEmail = react.useCallback(async () => {
|
|
347
409
|
setScreen({
|
|
348
410
|
value: "signIn"
|
|
349
411
|
});
|
|
350
412
|
}, [setScreen]);
|
|
413
|
+
const onSocialSignIn = react.useCallback(({
|
|
414
|
+
provider
|
|
415
|
+
}) => {
|
|
416
|
+
/**
|
|
417
|
+
* Kicks off Strapi's Users & Permissions provider flow. Strapi handles
|
|
418
|
+
* the OAuth exchange and redirects back to the frontend's configured
|
|
419
|
+
* redirect URL (e.g. `/connect/google/redirect`), which
|
|
420
|
+
* `AuthSocialSignInCallback` consumes.
|
|
421
|
+
*/
|
|
422
|
+
window.location.href = `${apiUrl}/connect/${provider.toLowerCase()}`;
|
|
423
|
+
}, [apiUrl]);
|
|
351
424
|
return /* @__PURE__ */(0, react_jsx_runtime.jsx)(_ttoss_react_auth_core.Auth, {
|
|
352
425
|
logo: props.logo,
|
|
353
426
|
layout: props.layout,
|
|
@@ -357,11 +430,87 @@ var Auth = props => {
|
|
|
357
430
|
onSignUp,
|
|
358
431
|
onForgotPassword,
|
|
359
432
|
onForgotPasswordResetPassword,
|
|
360
|
-
onConfirmSignUpCheckEmail
|
|
433
|
+
onConfirmSignUpCheckEmail,
|
|
434
|
+
socialProviders: props.socialProviders,
|
|
435
|
+
onSocialSignIn: props.socialProviders ? onSocialSignIn : void 0
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/AuthSocialSignInCallback.tsx
|
|
441
|
+
var GENERIC_ERROR_MESSAGE = "An error occurred during social sign in.";
|
|
442
|
+
var exchangeSocialSignInCallback = async ({
|
|
443
|
+
apiUrl,
|
|
444
|
+
provider
|
|
445
|
+
}) => {
|
|
446
|
+
const response = await fetch(`${apiUrl}/auth/${provider}/callback${window.location.search}`);
|
|
447
|
+
const data = await response.json();
|
|
448
|
+
if (!response.ok) throw new Error(data.error?.message || GENERIC_ERROR_MESSAGE);
|
|
449
|
+
if (data.refreshToken) storage.setRefreshToken(data.refreshToken);
|
|
450
|
+
return {
|
|
451
|
+
user: {
|
|
452
|
+
id: data.user.id,
|
|
453
|
+
email: data.user.email,
|
|
454
|
+
emailVerified: data.user.confirmed
|
|
455
|
+
},
|
|
456
|
+
tokens: {
|
|
457
|
+
accessToken: data.jwt,
|
|
458
|
+
refreshToken: data.refreshToken
|
|
459
|
+
},
|
|
460
|
+
isAuthenticated: true
|
|
461
|
+
};
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Mount this at the redirect route configured in Strapi's provider settings
|
|
465
|
+
* (e.g. `/connect/google/redirect`). It reads the callback query string
|
|
466
|
+
* Strapi appended to the redirect, forwards it to
|
|
467
|
+
* `${apiUrl}/auth/:provider/callback`, and authenticates the user with the
|
|
468
|
+
* resulting JWT — the same way `Auth`'s `onSignIn` does for email/password.
|
|
469
|
+
*/
|
|
470
|
+
var AuthSocialSignInCallback = props => {
|
|
471
|
+
const {
|
|
472
|
+
provider,
|
|
473
|
+
onSuccess,
|
|
474
|
+
onError,
|
|
475
|
+
children
|
|
476
|
+
} = props;
|
|
477
|
+
const {
|
|
478
|
+
apiUrl,
|
|
479
|
+
setAuthData
|
|
480
|
+
} = useAuth();
|
|
481
|
+
const {
|
|
482
|
+
addNotification
|
|
483
|
+
} = (0, _ttoss_react_notifications.useNotifications)();
|
|
484
|
+
react.useEffect(() => {
|
|
485
|
+
let cancelled = false;
|
|
486
|
+
exchangeSocialSignInCallback({
|
|
487
|
+
apiUrl,
|
|
488
|
+
provider
|
|
489
|
+
}).then(authData => {
|
|
490
|
+
if (cancelled) return;
|
|
491
|
+
setAuthData(authData);
|
|
492
|
+
onSuccess?.();
|
|
493
|
+
}).catch(error => {
|
|
494
|
+
if (cancelled) return;
|
|
495
|
+
const message = error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE;
|
|
496
|
+
addNotification({
|
|
497
|
+
title: "Sign in failed",
|
|
498
|
+
message,
|
|
499
|
+
type: "error"
|
|
500
|
+
});
|
|
501
|
+
onError?.(message);
|
|
502
|
+
});
|
|
503
|
+
return () => {
|
|
504
|
+
cancelled = true;
|
|
505
|
+
};
|
|
506
|
+
}, [apiUrl, provider, setAuthData, addNotification, onSuccess, onError]);
|
|
507
|
+
return /* @__PURE__ */(0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {
|
|
508
|
+
children
|
|
361
509
|
});
|
|
362
510
|
};
|
|
363
511
|
|
|
364
512
|
//#endregion
|
|
365
513
|
exports.Auth = Auth;
|
|
366
514
|
exports.AuthProvider = AuthProvider;
|
|
515
|
+
exports.AuthSocialSignInCallback = AuthSocialSignInCallback;
|
|
367
516
|
exports.useAuth = useAuth;
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import { AuthProps, AuthScreen } from "@ttoss/react-auth-core";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Auth.d.ts
|
|
6
|
-
declare const Auth: (props: Pick<AuthProps, "logo" | "layout"> & {
|
|
6
|
+
declare const Auth: (props: Pick<AuthProps, "logo" | "layout" | "socialProviders"> & {
|
|
7
7
|
initialScreen?: AuthScreen;
|
|
8
8
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
//#endregion
|
|
@@ -20,4 +20,20 @@ declare const useAuth: () => {
|
|
|
20
20
|
apiUrl: string;
|
|
21
21
|
};
|
|
22
22
|
//#endregion
|
|
23
|
-
|
|
23
|
+
//#region src/AuthSocialSignInCallback.d.ts
|
|
24
|
+
type AuthSocialSignInCallbackProps = {
|
|
25
|
+
/** Strapi provider slug, e.g. `"google"`, matching `/connect/:provider`. */provider: string; /** Called after the callback exchange sets the user as authenticated. */
|
|
26
|
+
onSuccess?: () => void; /** Called if the callback exchange fails, with a user-facing message. */
|
|
27
|
+
onError?: (message: string) => void; /** Rendered while the exchange is in flight (e.g. a spinner). */
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Mount this at the redirect route configured in Strapi's provider settings
|
|
32
|
+
* (e.g. `/connect/google/redirect`). It reads the callback query string
|
|
33
|
+
* Strapi appended to the redirect, forwards it to
|
|
34
|
+
* `${apiUrl}/auth/:provider/callback`, and authenticates the user with the
|
|
35
|
+
* resulting JWT — the same way `Auth`'s `onSignIn` does for email/password.
|
|
36
|
+
*/
|
|
37
|
+
declare const AuthSocialSignInCallback: (props: AuthSocialSignInCallbackProps) => import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Auth, AuthProvider, type AuthScreen, AuthSocialSignInCallback, AuthSocialSignInCallbackProps, useAuth };
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { AuthProps, AuthScreen } from "@ttoss/react-auth-core";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Auth.d.ts
|
|
6
|
-
declare const Auth: (props: Pick<AuthProps, "logo" | "layout"> & {
|
|
6
|
+
declare const Auth: (props: Pick<AuthProps, "logo" | "layout" | "socialProviders"> & {
|
|
7
7
|
initialScreen?: AuthScreen;
|
|
8
8
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
//#endregion
|
|
@@ -20,4 +20,20 @@ declare const useAuth: () => {
|
|
|
20
20
|
apiUrl: string;
|
|
21
21
|
};
|
|
22
22
|
//#endregion
|
|
23
|
-
|
|
23
|
+
//#region src/AuthSocialSignInCallback.d.ts
|
|
24
|
+
type AuthSocialSignInCallbackProps = {
|
|
25
|
+
/** Strapi provider slug, e.g. `"google"`, matching `/connect/:provider`. */provider: string; /** Called after the callback exchange sets the user as authenticated. */
|
|
26
|
+
onSuccess?: () => void; /** Called if the callback exchange fails, with a user-facing message. */
|
|
27
|
+
onError?: (message: string) => void; /** Rendered while the exchange is in flight (e.g. a spinner). */
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Mount this at the redirect route configured in Strapi's provider settings
|
|
32
|
+
* (e.g. `/connect/google/redirect`). It reads the callback query string
|
|
33
|
+
* Strapi appended to the redirect, forwards it to
|
|
34
|
+
* `${apiUrl}/auth/:provider/callback`, and authenticates the user with the
|
|
35
|
+
* resulting JWT — the same way `Auth`'s `onSignIn` does for email/password.
|
|
36
|
+
*/
|
|
37
|
+
declare const AuthSocialSignInCallback: (props: AuthSocialSignInCallbackProps) => import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Auth, AuthProvider, type AuthScreen, AuthSocialSignInCallback, AuthSocialSignInCallbackProps, useAuth };
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Auth as Auth$1, AuthProvider as AuthProvider$1, useAuth as useAuth$1, useAuthScreen } from "@ttoss/react-auth-core";
|
|
3
3
|
import { useNotifications } from "@ttoss/react-notifications";
|
|
4
4
|
import * as React from "react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/storage.ts
|
|
8
8
|
var AUTH_STORAGE_REFRESH_TOKEN_KEY = "ttoss-strapi-auth-refresh-token";
|
|
@@ -118,6 +118,148 @@ var useAuth = () => {
|
|
|
118
118
|
|
|
119
119
|
//#endregion
|
|
120
120
|
//#region src/Auth.tsx
|
|
121
|
+
var GENERIC_ERROR_MESSAGE$1 = "Unable to connect to the server. Please check your connection.";
|
|
122
|
+
var signInWithEmail = async ({
|
|
123
|
+
apiUrl,
|
|
124
|
+
email,
|
|
125
|
+
password,
|
|
126
|
+
notifyError,
|
|
127
|
+
onUnconfirmedEmail
|
|
128
|
+
}) => {
|
|
129
|
+
const response = await fetch(`${apiUrl}/auth/local`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
"Content-Type": "application/json"
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
identifier: email,
|
|
136
|
+
password
|
|
137
|
+
})
|
|
138
|
+
});
|
|
139
|
+
const data = await response.json();
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
if (data.error?.message === "Your account email is not confirmed") {
|
|
142
|
+
await onUnconfirmedEmail();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
notifyError({
|
|
146
|
+
title: "Sign in failed",
|
|
147
|
+
message: data.error?.message || "An error occurred during sign in."
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
storage.setRefreshToken(data.refreshToken);
|
|
152
|
+
return {
|
|
153
|
+
user: {
|
|
154
|
+
id: data.user.id,
|
|
155
|
+
email: data.user.email,
|
|
156
|
+
emailVerified: data.user.confirmed
|
|
157
|
+
},
|
|
158
|
+
tokens: {
|
|
159
|
+
accessToken: data.jwt,
|
|
160
|
+
refreshToken: data.refreshToken
|
|
161
|
+
},
|
|
162
|
+
isAuthenticated: true
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
var resendEmailConfirmation = async ({
|
|
166
|
+
apiUrl,
|
|
167
|
+
email,
|
|
168
|
+
notifyError
|
|
169
|
+
}) => {
|
|
170
|
+
const response = await fetch(`${apiUrl}/auth/send-email-confirmation`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json"
|
|
174
|
+
},
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
email
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
notifyError({
|
|
181
|
+
title: "Resend confirmation email failed",
|
|
182
|
+
message: (await response.json()).error?.message || "An error occurred while resending the confirmation email."
|
|
183
|
+
});
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
var registerWithEmail = async ({
|
|
189
|
+
apiUrl,
|
|
190
|
+
email,
|
|
191
|
+
password,
|
|
192
|
+
notifyError
|
|
193
|
+
}) => {
|
|
194
|
+
const response = await fetch(`${apiUrl}/auth/local/register`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/json"
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
username: email,
|
|
201
|
+
email,
|
|
202
|
+
password
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
notifyError({
|
|
207
|
+
title: "Sign up failed",
|
|
208
|
+
message: (await response.json()).error?.message || "An error occurred during sign up."
|
|
209
|
+
});
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
};
|
|
214
|
+
var requestPasswordReset = async ({
|
|
215
|
+
apiUrl,
|
|
216
|
+
email,
|
|
217
|
+
notifyError
|
|
218
|
+
}) => {
|
|
219
|
+
const response = await fetch(`${apiUrl}/auth/forgot-password`, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "application/json"
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({
|
|
225
|
+
email
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
notifyError({
|
|
230
|
+
title: "Forgot password failed",
|
|
231
|
+
message: (await response.json()).error?.message || "An error occurred during forgot password."
|
|
232
|
+
});
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
};
|
|
237
|
+
var resetPasswordWithCode = async ({
|
|
238
|
+
apiUrl,
|
|
239
|
+
code,
|
|
240
|
+
newPassword,
|
|
241
|
+
notifyError
|
|
242
|
+
}) => {
|
|
243
|
+
const response = await fetch(`${apiUrl}/auth/reset-password`, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: {
|
|
246
|
+
"Content-Type": "application/json"
|
|
247
|
+
},
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
code,
|
|
250
|
+
password: newPassword,
|
|
251
|
+
passwordConfirmation: newPassword
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
notifyError({
|
|
256
|
+
title: "Reset password failed",
|
|
257
|
+
message: (await response.json()).error?.message || "An error occurred during password reset."
|
|
258
|
+
});
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
};
|
|
121
263
|
var Auth = props => {
|
|
122
264
|
const {
|
|
123
265
|
setAuthData,
|
|
@@ -130,193 +272,124 @@ var Auth = props => {
|
|
|
130
272
|
const {
|
|
131
273
|
addNotification
|
|
132
274
|
} = useNotifications();
|
|
275
|
+
const notifyError = React.useCallback(({
|
|
276
|
+
title,
|
|
277
|
+
message
|
|
278
|
+
}) => {
|
|
279
|
+
addNotification({
|
|
280
|
+
title,
|
|
281
|
+
message: message || GENERIC_ERROR_MESSAGE$1,
|
|
282
|
+
type: "error"
|
|
283
|
+
});
|
|
284
|
+
}, [addNotification]);
|
|
133
285
|
const onSignIn = React.useCallback(async ({
|
|
134
286
|
email,
|
|
135
287
|
password
|
|
136
288
|
}) => {
|
|
137
289
|
try {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!response.ok) {
|
|
150
|
-
if (data.error?.message === "Your account email is not confirmed") {
|
|
151
|
-
const resendResponse = await fetch(`${apiUrl}/auth/send-email-confirmation`, {
|
|
152
|
-
method: "POST",
|
|
153
|
-
headers: {
|
|
154
|
-
"Content-Type": "application/json"
|
|
155
|
-
},
|
|
156
|
-
body: JSON.stringify({
|
|
157
|
-
email
|
|
158
|
-
})
|
|
159
|
-
});
|
|
160
|
-
if (!resendResponse.ok) {
|
|
161
|
-
addNotification({
|
|
162
|
-
title: "Resend confirmation email failed",
|
|
163
|
-
message: (await resendResponse.json()).error?.message || "An error occurred while resending the confirmation email.",
|
|
164
|
-
type: "error"
|
|
165
|
-
});
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
setScreen({
|
|
290
|
+
const authData = await signInWithEmail({
|
|
291
|
+
apiUrl,
|
|
292
|
+
email,
|
|
293
|
+
password,
|
|
294
|
+
notifyError,
|
|
295
|
+
onUnconfirmedEmail: async () => {
|
|
296
|
+
if (await resendEmailConfirmation({
|
|
297
|
+
apiUrl,
|
|
298
|
+
email,
|
|
299
|
+
notifyError
|
|
300
|
+
})) setScreen({
|
|
169
301
|
value: "confirmSignUpCheckEmail"
|
|
170
302
|
});
|
|
171
|
-
return;
|
|
172
303
|
}
|
|
173
|
-
addNotification({
|
|
174
|
-
title: "Sign in failed",
|
|
175
|
-
message: data.error?.message || "An error occurred during sign in.",
|
|
176
|
-
type: "error"
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
storage.setRefreshToken(data.refreshToken);
|
|
181
|
-
setAuthData({
|
|
182
|
-
user: {
|
|
183
|
-
id: data.user.id,
|
|
184
|
-
email: data.user.email,
|
|
185
|
-
emailVerified: data.user.confirmed
|
|
186
|
-
},
|
|
187
|
-
tokens: {
|
|
188
|
-
accessToken: data.jwt,
|
|
189
|
-
refreshToken: data.refreshToken
|
|
190
|
-
},
|
|
191
|
-
isAuthenticated: true
|
|
192
304
|
});
|
|
305
|
+
if (authData) setAuthData(authData);
|
|
193
306
|
} catch {
|
|
194
|
-
|
|
195
|
-
title: "Network Error"
|
|
196
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
197
|
-
type: "error"
|
|
307
|
+
notifyError({
|
|
308
|
+
title: "Network Error"
|
|
198
309
|
});
|
|
199
310
|
}
|
|
200
|
-
}, [setAuthData, setScreen,
|
|
311
|
+
}, [setAuthData, setScreen, notifyError, apiUrl]);
|
|
201
312
|
const onSignUp = React.useCallback(async ({
|
|
202
313
|
email,
|
|
203
314
|
password
|
|
204
315
|
}) => {
|
|
205
316
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
username: email,
|
|
213
|
-
email,
|
|
214
|
-
password
|
|
215
|
-
})
|
|
216
|
-
});
|
|
217
|
-
const data = await response.json();
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
addNotification({
|
|
220
|
-
title: "Sign up failed",
|
|
221
|
-
message: data.error?.message || "An error occurred during sign up.",
|
|
222
|
-
type: "error"
|
|
223
|
-
});
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
setScreen({
|
|
317
|
+
if (await registerWithEmail({
|
|
318
|
+
apiUrl,
|
|
319
|
+
email,
|
|
320
|
+
password,
|
|
321
|
+
notifyError
|
|
322
|
+
})) setScreen({
|
|
227
323
|
value: "confirmSignUpCheckEmail"
|
|
228
324
|
});
|
|
229
325
|
} catch {
|
|
230
|
-
|
|
231
|
-
title: "Network Error"
|
|
232
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
233
|
-
type: "error"
|
|
326
|
+
notifyError({
|
|
327
|
+
title: "Network Error"
|
|
234
328
|
});
|
|
235
329
|
}
|
|
236
|
-
}, [
|
|
330
|
+
}, [setScreen, notifyError, apiUrl]);
|
|
237
331
|
const onForgotPassword = React.useCallback(async ({
|
|
238
332
|
email
|
|
239
333
|
}) => {
|
|
240
334
|
try {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
body: JSON.stringify({
|
|
247
|
-
email
|
|
248
|
-
})
|
|
249
|
-
});
|
|
250
|
-
const data = await response.json();
|
|
251
|
-
if (!response.ok) {
|
|
252
|
-
addNotification({
|
|
253
|
-
title: "Forgot password failed",
|
|
254
|
-
message: data.error?.message || "An error occurred during forgot password.",
|
|
255
|
-
type: "error"
|
|
256
|
-
});
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
setScreen({
|
|
335
|
+
if (await requestPasswordReset({
|
|
336
|
+
apiUrl,
|
|
337
|
+
email,
|
|
338
|
+
notifyError
|
|
339
|
+
})) setScreen({
|
|
260
340
|
value: "confirmResetPassword",
|
|
261
341
|
context: {
|
|
262
342
|
email
|
|
263
343
|
}
|
|
264
344
|
});
|
|
265
345
|
} catch {
|
|
266
|
-
|
|
267
|
-
title: "Network Error"
|
|
268
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
269
|
-
type: "error"
|
|
346
|
+
notifyError({
|
|
347
|
+
title: "Network Error"
|
|
270
348
|
});
|
|
271
349
|
}
|
|
272
|
-
}, [
|
|
350
|
+
}, [setScreen, notifyError, apiUrl]);
|
|
273
351
|
const onForgotPasswordResetPassword = React.useCallback(async ({
|
|
274
|
-
email: _email,
|
|
275
352
|
code,
|
|
276
353
|
newPassword
|
|
277
354
|
}) => {
|
|
278
355
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
code,
|
|
286
|
-
password: newPassword,
|
|
287
|
-
passwordConfirmation: newPassword
|
|
288
|
-
})
|
|
289
|
-
});
|
|
290
|
-
const data = await response.json();
|
|
291
|
-
if (!response.ok) {
|
|
356
|
+
if (await resetPasswordWithCode({
|
|
357
|
+
apiUrl,
|
|
358
|
+
code,
|
|
359
|
+
newPassword,
|
|
360
|
+
notifyError
|
|
361
|
+
})) {
|
|
292
362
|
addNotification({
|
|
293
|
-
title: "
|
|
294
|
-
message:
|
|
295
|
-
type: "
|
|
363
|
+
title: "Password reset successful",
|
|
364
|
+
message: "You can now sign in with your new password.",
|
|
365
|
+
type: "success"
|
|
366
|
+
});
|
|
367
|
+
setScreen({
|
|
368
|
+
value: "signIn"
|
|
296
369
|
});
|
|
297
|
-
return;
|
|
298
370
|
}
|
|
299
|
-
addNotification({
|
|
300
|
-
title: "Password reset successful",
|
|
301
|
-
message: "You can now sign in with your new password.",
|
|
302
|
-
type: "success"
|
|
303
|
-
});
|
|
304
|
-
setScreen({
|
|
305
|
-
value: "signIn"
|
|
306
|
-
});
|
|
307
371
|
} catch {
|
|
308
|
-
|
|
309
|
-
title: "Network Error"
|
|
310
|
-
message: "Unable to connect to the server. Please check your connection.",
|
|
311
|
-
type: "error"
|
|
372
|
+
notifyError({
|
|
373
|
+
title: "Network Error"
|
|
312
374
|
});
|
|
313
375
|
}
|
|
314
|
-
}, [
|
|
376
|
+
}, [setScreen, notifyError, addNotification, apiUrl]);
|
|
315
377
|
const onConfirmSignUpCheckEmail = React.useCallback(async () => {
|
|
316
378
|
setScreen({
|
|
317
379
|
value: "signIn"
|
|
318
380
|
});
|
|
319
381
|
}, [setScreen]);
|
|
382
|
+
const onSocialSignIn = React.useCallback(({
|
|
383
|
+
provider
|
|
384
|
+
}) => {
|
|
385
|
+
/**
|
|
386
|
+
* Kicks off Strapi's Users & Permissions provider flow. Strapi handles
|
|
387
|
+
* the OAuth exchange and redirects back to the frontend's configured
|
|
388
|
+
* redirect URL (e.g. `/connect/google/redirect`), which
|
|
389
|
+
* `AuthSocialSignInCallback` consumes.
|
|
390
|
+
*/
|
|
391
|
+
window.location.href = `${apiUrl}/connect/${provider.toLowerCase()}`;
|
|
392
|
+
}, [apiUrl]);
|
|
320
393
|
return /* @__PURE__ */jsx(Auth$1, {
|
|
321
394
|
logo: props.logo,
|
|
322
395
|
layout: props.layout,
|
|
@@ -326,9 +399,84 @@ var Auth = props => {
|
|
|
326
399
|
onSignUp,
|
|
327
400
|
onForgotPassword,
|
|
328
401
|
onForgotPasswordResetPassword,
|
|
329
|
-
onConfirmSignUpCheckEmail
|
|
402
|
+
onConfirmSignUpCheckEmail,
|
|
403
|
+
socialProviders: props.socialProviders,
|
|
404
|
+
onSocialSignIn: props.socialProviders ? onSocialSignIn : void 0
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/AuthSocialSignInCallback.tsx
|
|
410
|
+
var GENERIC_ERROR_MESSAGE = "An error occurred during social sign in.";
|
|
411
|
+
var exchangeSocialSignInCallback = async ({
|
|
412
|
+
apiUrl,
|
|
413
|
+
provider
|
|
414
|
+
}) => {
|
|
415
|
+
const response = await fetch(`${apiUrl}/auth/${provider}/callback${window.location.search}`);
|
|
416
|
+
const data = await response.json();
|
|
417
|
+
if (!response.ok) throw new Error(data.error?.message || GENERIC_ERROR_MESSAGE);
|
|
418
|
+
if (data.refreshToken) storage.setRefreshToken(data.refreshToken);
|
|
419
|
+
return {
|
|
420
|
+
user: {
|
|
421
|
+
id: data.user.id,
|
|
422
|
+
email: data.user.email,
|
|
423
|
+
emailVerified: data.user.confirmed
|
|
424
|
+
},
|
|
425
|
+
tokens: {
|
|
426
|
+
accessToken: data.jwt,
|
|
427
|
+
refreshToken: data.refreshToken
|
|
428
|
+
},
|
|
429
|
+
isAuthenticated: true
|
|
430
|
+
};
|
|
431
|
+
};
|
|
432
|
+
/**
|
|
433
|
+
* Mount this at the redirect route configured in Strapi's provider settings
|
|
434
|
+
* (e.g. `/connect/google/redirect`). It reads the callback query string
|
|
435
|
+
* Strapi appended to the redirect, forwards it to
|
|
436
|
+
* `${apiUrl}/auth/:provider/callback`, and authenticates the user with the
|
|
437
|
+
* resulting JWT — the same way `Auth`'s `onSignIn` does for email/password.
|
|
438
|
+
*/
|
|
439
|
+
var AuthSocialSignInCallback = props => {
|
|
440
|
+
const {
|
|
441
|
+
provider,
|
|
442
|
+
onSuccess,
|
|
443
|
+
onError,
|
|
444
|
+
children
|
|
445
|
+
} = props;
|
|
446
|
+
const {
|
|
447
|
+
apiUrl,
|
|
448
|
+
setAuthData
|
|
449
|
+
} = useAuth();
|
|
450
|
+
const {
|
|
451
|
+
addNotification
|
|
452
|
+
} = useNotifications();
|
|
453
|
+
React.useEffect(() => {
|
|
454
|
+
let cancelled = false;
|
|
455
|
+
exchangeSocialSignInCallback({
|
|
456
|
+
apiUrl,
|
|
457
|
+
provider
|
|
458
|
+
}).then(authData => {
|
|
459
|
+
if (cancelled) return;
|
|
460
|
+
setAuthData(authData);
|
|
461
|
+
onSuccess?.();
|
|
462
|
+
}).catch(error => {
|
|
463
|
+
if (cancelled) return;
|
|
464
|
+
const message = error instanceof Error ? error.message : GENERIC_ERROR_MESSAGE;
|
|
465
|
+
addNotification({
|
|
466
|
+
title: "Sign in failed",
|
|
467
|
+
message,
|
|
468
|
+
type: "error"
|
|
469
|
+
});
|
|
470
|
+
onError?.(message);
|
|
471
|
+
});
|
|
472
|
+
return () => {
|
|
473
|
+
cancelled = true;
|
|
474
|
+
};
|
|
475
|
+
}, [apiUrl, provider, setAuthData, addNotification, onSuccess, onError]);
|
|
476
|
+
return /* @__PURE__ */jsx(Fragment, {
|
|
477
|
+
children
|
|
330
478
|
});
|
|
331
479
|
};
|
|
332
480
|
|
|
333
481
|
//#endregion
|
|
334
|
-
export { Auth, AuthProvider, useAuth };
|
|
482
|
+
export { Auth, AuthProvider, AuthSocialSignInCallback, useAuth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/react-auth-strapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Authentication components and abstractions for React apps using Strapi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"React",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"tsdown": "^0.22.2",
|
|
42
42
|
"@ttoss/config": "^1.38.0",
|
|
43
43
|
"@ttoss/i18n-cli": "^0.9.0",
|
|
44
|
-
"@ttoss/react-auth-core": "^0.7.
|
|
45
|
-
"@ttoss/react-
|
|
46
|
-
"@ttoss/react-notifications": "^2.8.5",
|
|
44
|
+
"@ttoss/react-auth-core": "^0.7.1",
|
|
45
|
+
"@ttoss/react-notifications": "^2.8.6",
|
|
47
46
|
"@ttoss/test-utils": "^4.2.19",
|
|
48
|
-
"@ttoss/ui": "^6.9.
|
|
47
|
+
"@ttoss/ui": "^6.9.30",
|
|
48
|
+
"@ttoss/react-i18n": "^2.3.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": ">=16.8.0",
|
|
52
|
-
"@ttoss/react-auth-core": "^0.7.
|
|
53
|
-
"@ttoss/react-notifications": "^2.8.
|
|
52
|
+
"@ttoss/react-auth-core": "^0.7.1",
|
|
53
|
+
"@ttoss/react-notifications": "^2.8.6",
|
|
54
54
|
"@ttoss/react-i18n": "^2.3.1"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|