@seaverse/auth-sdk 0.2.3 → 0.2.4
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 +32 -2
- package/dist/index.cjs +170 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +170 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,7 @@ const registerResult = await client.register({
|
|
|
94
94
|
password: 'SecurePassword123',
|
|
95
95
|
username: 'johndoe', // 可选,未提供则从邮箱自动生成
|
|
96
96
|
invitation_code: 'INVITE123', // 可选
|
|
97
|
+
frontend_url: 'https://mygame.com/verify', // 可选,邮箱验证链接的前端URL,默认为 window.location.origin
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
// 检查注册结果
|
|
@@ -125,6 +126,7 @@ await client.logout();
|
|
|
125
126
|
// 忘记密码
|
|
126
127
|
await client.forgotPassword({
|
|
127
128
|
email: 'user@example.com',
|
|
129
|
+
frontend_url: 'https://mygame.com/', // 可选,默认为 window.location.origin
|
|
128
130
|
});
|
|
129
131
|
|
|
130
132
|
// 重置密码
|
|
@@ -270,6 +272,34 @@ authModal.hide();
|
|
|
270
272
|
authModal.destroy();
|
|
271
273
|
```
|
|
272
274
|
|
|
275
|
+
#### 重置密码流程
|
|
276
|
+
|
|
277
|
+
AuthModal 支持完整的密码重置流程:
|
|
278
|
+
|
|
279
|
+
1. **用户触发忘记密码**:在登录界面点击 "Forgot Password?" 链接
|
|
280
|
+
2. **发送重置邮件**:输入邮箱后,系统发送带有 `verify_token` 的重置链接
|
|
281
|
+
3. **自动唤起重置弹窗**:用户点击邮件中的链接返回网站时,AuthModal 会自动检测 URL 中的 `verify_token` 参数并显示重置密码表单
|
|
282
|
+
4. **设置新密码**:用户输入并确认新密码后提交
|
|
283
|
+
5. **自动清理 URL**:重置成功后自动清除 URL 中的 `verify_token` 参数
|
|
284
|
+
|
|
285
|
+
整个流程无需额外代码,AuthModal 会自动处理:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// 1. 初始化 AuthModal(只需一次)
|
|
289
|
+
const authModal = new AuthModal({
|
|
290
|
+
client,
|
|
291
|
+
// ... 其他配置
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// 2. 用户点击邮件中的重置链接后,AuthModal 会自动:
|
|
295
|
+
// - 检测 URL 中的 ?verify_token=xxx 参数
|
|
296
|
+
// - 显示重置密码表单
|
|
297
|
+
// - 用户提交新密码
|
|
298
|
+
// - 调用 client.resetPassword() API
|
|
299
|
+
// - 清理 URL 中的 verify_token 参数
|
|
300
|
+
// - 显示成功消息
|
|
301
|
+
```
|
|
302
|
+
|
|
273
303
|
#### OAuth 配置说明
|
|
274
304
|
|
|
275
305
|
`enableOAuth` 参数是**完全可选的**:
|
|
@@ -519,11 +549,11 @@ SDK支持以下环境:
|
|
|
519
549
|
|
|
520
550
|
| 方法 | 参数 | 返回值 | 说明 |
|
|
521
551
|
|------|------|--------|------|
|
|
522
|
-
| `register()` | `{ email, password, username?, invitation_code? }` | `RegisterResponse` |
|
|
552
|
+
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` | 注册新用户,frontend_url 为邮箱验证链接的前端URL,默认为 window.location.origin |
|
|
523
553
|
| `login()` | `{ email, password }` | `LoginResponse` | 用户登录 |
|
|
524
554
|
| `getCurrentUser()` | - | `User` | 获取当前用户 |
|
|
525
555
|
| `logout()` | - | `SuccessResponse` | 登出 |
|
|
526
|
-
| `forgotPassword()` | `{ email }` | `SuccessResponse` |
|
|
556
|
+
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` | 忘记密码,frontend_url 默认为 window.location.origin |
|
|
527
557
|
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` | 重置密码 |
|
|
528
558
|
| `setToken()` | `token: string` | `void` | 设置认证 token(OAuth 登录后使用) |
|
|
529
559
|
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` | 获取API Token |
|
package/dist/index.cjs
CHANGED
|
@@ -1304,10 +1304,12 @@ class SeaVerseBackendAPIClient {
|
|
|
1304
1304
|
* Register a new user with email verification
|
|
1305
1305
|
*/
|
|
1306
1306
|
async register(data, options) {
|
|
1307
|
+
// 如果没有传 frontend_url,使用当前页面地址
|
|
1308
|
+
const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
1307
1309
|
const config = {
|
|
1308
1310
|
method: 'POST',
|
|
1309
1311
|
url: `/sdk/v1/auth/register`,
|
|
1310
|
-
data,
|
|
1312
|
+
data: { ...data, frontend_url },
|
|
1311
1313
|
headers: {
|
|
1312
1314
|
'X-Operation-Id': 'register',
|
|
1313
1315
|
...options?.headers,
|
|
@@ -1374,10 +1376,12 @@ class SeaVerseBackendAPIClient {
|
|
|
1374
1376
|
* Send password reset email
|
|
1375
1377
|
*/
|
|
1376
1378
|
async forgotPassword(data, options) {
|
|
1379
|
+
// 如果没有传 frontend_url,使用当前页面地址
|
|
1380
|
+
const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
1377
1381
|
const config = {
|
|
1378
1382
|
method: 'POST',
|
|
1379
1383
|
url: `/sdk/v1/auth/forgot-password`,
|
|
1380
|
-
data,
|
|
1384
|
+
data: { ...data, frontend_url },
|
|
1381
1385
|
headers: {
|
|
1382
1386
|
'X-Operation-Id': 'forgotPassword',
|
|
1383
1387
|
...options?.headers,
|
|
@@ -1814,8 +1818,11 @@ class AuthModal {
|
|
|
1814
1818
|
constructor(options) {
|
|
1815
1819
|
this.modal = null;
|
|
1816
1820
|
this.currentView = 'login';
|
|
1821
|
+
this.resetToken = null;
|
|
1817
1822
|
this.client = options.client;
|
|
1818
1823
|
this.options = options;
|
|
1824
|
+
// Auto-detect reset token in URL
|
|
1825
|
+
this.checkForResetToken();
|
|
1819
1826
|
}
|
|
1820
1827
|
/**
|
|
1821
1828
|
* Show the authentication modal
|
|
@@ -1923,6 +1930,9 @@ class AuthModal {
|
|
|
1923
1930
|
// Forgot password form
|
|
1924
1931
|
const forgotForm = this.createForgotPasswordForm();
|
|
1925
1932
|
rightPanel.appendChild(forgotForm);
|
|
1933
|
+
// Reset password form
|
|
1934
|
+
const resetForm = this.createResetPasswordForm();
|
|
1935
|
+
rightPanel.appendChild(resetForm);
|
|
1926
1936
|
// Success message
|
|
1927
1937
|
const successMessage = this.createSuccessMessage();
|
|
1928
1938
|
rightPanel.appendChild(successMessage);
|
|
@@ -2176,6 +2186,86 @@ class AuthModal {
|
|
|
2176
2186
|
container.appendChild(footer);
|
|
2177
2187
|
return container;
|
|
2178
2188
|
}
|
|
2189
|
+
createResetPasswordForm() {
|
|
2190
|
+
const container = document.createElement('div');
|
|
2191
|
+
container.id = 'resetPasswordForm';
|
|
2192
|
+
container.className = 'auth-form-view hidden';
|
|
2193
|
+
// Icon
|
|
2194
|
+
const icon = document.createElement('div');
|
|
2195
|
+
icon.className = 'forgot-password-icon';
|
|
2196
|
+
const iconContent = '<div class="icon-glow"></div><svg class="icon-lock" width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path class="lock-shackle" d="M7 11V7a5 5 0 0 1 10 0v4"/><circle class="lock-keyhole" cx="12" cy="16" r="1.5"/></svg>';
|
|
2197
|
+
icon.innerHTML = iconContent;
|
|
2198
|
+
container.appendChild(icon);
|
|
2199
|
+
// Header
|
|
2200
|
+
const header = document.createElement('div');
|
|
2201
|
+
header.className = 'auth-form-header';
|
|
2202
|
+
const title = document.createElement('h2');
|
|
2203
|
+
title.className = 'auth-form-title';
|
|
2204
|
+
title.textContent = 'Set New Password';
|
|
2205
|
+
const subtitle = document.createElement('p');
|
|
2206
|
+
subtitle.className = 'auth-form-subtitle';
|
|
2207
|
+
subtitle.textContent = 'Enter your new password below';
|
|
2208
|
+
header.appendChild(title);
|
|
2209
|
+
header.appendChild(subtitle);
|
|
2210
|
+
container.appendChild(header);
|
|
2211
|
+
// Form
|
|
2212
|
+
const form = document.createElement('form');
|
|
2213
|
+
form.id = 'resetPasswordFormElement';
|
|
2214
|
+
form.className = 'auth-form';
|
|
2215
|
+
// New password field
|
|
2216
|
+
const passwordGroup = document.createElement('div');
|
|
2217
|
+
passwordGroup.className = 'form-group';
|
|
2218
|
+
const passwordLabel = document.createElement('label');
|
|
2219
|
+
passwordLabel.htmlFor = 'newPassword';
|
|
2220
|
+
passwordLabel.className = 'form-label';
|
|
2221
|
+
passwordLabel.textContent = 'New Password';
|
|
2222
|
+
passwordGroup.appendChild(passwordLabel);
|
|
2223
|
+
const passwordInputWrapper = this.createPasswordInput('newPassword', 'Enter new password');
|
|
2224
|
+
passwordGroup.appendChild(passwordInputWrapper);
|
|
2225
|
+
const strengthText = document.createElement('p');
|
|
2226
|
+
strengthText.className = 'strength-text';
|
|
2227
|
+
strengthText.textContent = 'Use 8+ characters with mix of letters, numbers & symbols';
|
|
2228
|
+
passwordGroup.appendChild(strengthText);
|
|
2229
|
+
form.appendChild(passwordGroup);
|
|
2230
|
+
// Confirm password field
|
|
2231
|
+
const confirmGroup = document.createElement('div');
|
|
2232
|
+
confirmGroup.className = 'form-group';
|
|
2233
|
+
const confirmLabel = document.createElement('label');
|
|
2234
|
+
confirmLabel.htmlFor = 'newPasswordConfirm';
|
|
2235
|
+
confirmLabel.className = 'form-label';
|
|
2236
|
+
confirmLabel.textContent = 'Confirm New Password';
|
|
2237
|
+
confirmGroup.appendChild(confirmLabel);
|
|
2238
|
+
const confirmInputWrapper = this.createPasswordInput('newPasswordConfirm', 'Confirm new password');
|
|
2239
|
+
confirmGroup.appendChild(confirmInputWrapper);
|
|
2240
|
+
form.appendChild(confirmGroup);
|
|
2241
|
+
const submitBtn = document.createElement('button');
|
|
2242
|
+
submitBtn.type = 'submit';
|
|
2243
|
+
submitBtn.id = 'resetPasswordButton';
|
|
2244
|
+
submitBtn.className = 'btn-auth-primary btn-forgot-password';
|
|
2245
|
+
const btnText = document.createElement('span');
|
|
2246
|
+
btnText.className = 'btn-text';
|
|
2247
|
+
btnText.textContent = 'Reset Password';
|
|
2248
|
+
const btnLoader = document.createElement('span');
|
|
2249
|
+
btnLoader.className = 'btn-loader hidden';
|
|
2250
|
+
const spinnerSVG = '<svg class="spinner" viewBox="0 0 24 24"><circle class="spinner-track" cx="12" cy="12" r="10"></circle><circle class="spinner-circle" cx="12" cy="12" r="10"></circle></svg>';
|
|
2251
|
+
btnLoader.innerHTML = spinnerSVG;
|
|
2252
|
+
submitBtn.appendChild(btnText);
|
|
2253
|
+
submitBtn.appendChild(btnLoader);
|
|
2254
|
+
form.appendChild(submitBtn);
|
|
2255
|
+
container.appendChild(form);
|
|
2256
|
+
// Footer
|
|
2257
|
+
const footer = document.createElement('div');
|
|
2258
|
+
footer.className = 'auth-footer forgot-footer';
|
|
2259
|
+
const backLink = document.createElement('a');
|
|
2260
|
+
backLink.href = '#';
|
|
2261
|
+
backLink.id = 'backToLoginFromReset';
|
|
2262
|
+
backLink.className = 'back-to-login';
|
|
2263
|
+
const backLinkHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg><span>Back to Sign In</span>';
|
|
2264
|
+
backLink.innerHTML = backLinkHTML;
|
|
2265
|
+
footer.appendChild(backLink);
|
|
2266
|
+
container.appendChild(footer);
|
|
2267
|
+
return container;
|
|
2268
|
+
}
|
|
2179
2269
|
createSuccessMessage() {
|
|
2180
2270
|
const container = document.createElement('div');
|
|
2181
2271
|
container.id = 'authMessage';
|
|
@@ -2265,6 +2355,20 @@ class AuthModal {
|
|
|
2265
2355
|
button.appendChild(span);
|
|
2266
2356
|
return button;
|
|
2267
2357
|
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Check if URL contains reset token and auto-show reset password form
|
|
2360
|
+
*/
|
|
2361
|
+
checkForResetToken() {
|
|
2362
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2363
|
+
const verifyToken = urlParams.get('verify_token');
|
|
2364
|
+
if (verifyToken) {
|
|
2365
|
+
this.resetToken = verifyToken;
|
|
2366
|
+
// Auto-show modal with reset password form
|
|
2367
|
+
setTimeout(() => {
|
|
2368
|
+
this.show('reset-password');
|
|
2369
|
+
}, 100);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2268
2372
|
bindEventListeners() {
|
|
2269
2373
|
if (!this.modal)
|
|
2270
2374
|
return;
|
|
@@ -2295,6 +2399,11 @@ class AuthModal {
|
|
|
2295
2399
|
e.preventDefault();
|
|
2296
2400
|
this.switchView('login');
|
|
2297
2401
|
});
|
|
2402
|
+
const backToLoginFromReset = this.modal.querySelector('#backToLoginFromReset');
|
|
2403
|
+
backToLoginFromReset?.addEventListener('click', (e) => {
|
|
2404
|
+
e.preventDefault();
|
|
2405
|
+
this.switchView('login');
|
|
2406
|
+
});
|
|
2298
2407
|
// Password toggle
|
|
2299
2408
|
const passwordToggles = this.modal.querySelectorAll('.toggle-password');
|
|
2300
2409
|
passwordToggles.forEach((toggle) => {
|
|
@@ -2325,6 +2434,8 @@ class AuthModal {
|
|
|
2325
2434
|
signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
|
|
2326
2435
|
const forgotForm = this.modal.querySelector('#forgotPasswordFormElement');
|
|
2327
2436
|
forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
|
|
2437
|
+
const resetPasswordForm = this.modal.querySelector('#resetPasswordFormElement');
|
|
2438
|
+
resetPasswordForm?.addEventListener('submit', (e) => this.handleResetPassword(e));
|
|
2328
2439
|
// OAuth social login buttons
|
|
2329
2440
|
this.bindSocialLoginButtons();
|
|
2330
2441
|
}
|
|
@@ -2371,7 +2482,7 @@ class AuthModal {
|
|
|
2371
2482
|
switchView(view) {
|
|
2372
2483
|
if (!this.modal)
|
|
2373
2484
|
return;
|
|
2374
|
-
const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'authMessage'];
|
|
2485
|
+
const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'resetPasswordForm', 'authMessage'];
|
|
2375
2486
|
views.forEach((viewId) => {
|
|
2376
2487
|
const element = this.modal.querySelector(`#${viewId}`);
|
|
2377
2488
|
element?.classList.add('hidden');
|
|
@@ -2380,6 +2491,7 @@ class AuthModal {
|
|
|
2380
2491
|
login: 'loginForm',
|
|
2381
2492
|
signup: 'signupForm',
|
|
2382
2493
|
forgot: 'forgotPasswordForm',
|
|
2494
|
+
'reset-password': 'resetPasswordForm',
|
|
2383
2495
|
message: 'authMessage',
|
|
2384
2496
|
};
|
|
2385
2497
|
const targetView = this.modal.querySelector(`#${viewMap[view]}`);
|
|
@@ -2528,6 +2640,61 @@ class AuthModal {
|
|
|
2528
2640
|
submitBtn.disabled = false;
|
|
2529
2641
|
}
|
|
2530
2642
|
}
|
|
2643
|
+
async handleResetPassword(e) {
|
|
2644
|
+
e.preventDefault();
|
|
2645
|
+
const passwordInput = this.modal?.querySelector('#newPassword');
|
|
2646
|
+
const passwordConfirmInput = this.modal?.querySelector('#newPasswordConfirm');
|
|
2647
|
+
const submitBtn = this.modal?.querySelector('#resetPasswordButton');
|
|
2648
|
+
const btnText = submitBtn?.querySelector('.btn-text');
|
|
2649
|
+
const btnLoader = submitBtn?.querySelector('.btn-loader');
|
|
2650
|
+
if (!passwordInput || !passwordConfirmInput || !submitBtn)
|
|
2651
|
+
return;
|
|
2652
|
+
const newPassword = passwordInput.value;
|
|
2653
|
+
const confirmPassword = passwordConfirmInput.value;
|
|
2654
|
+
// Validate passwords match
|
|
2655
|
+
if (newPassword !== confirmPassword) {
|
|
2656
|
+
this.showError('Passwords do not match');
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
// Validate token exists
|
|
2660
|
+
if (!this.resetToken) {
|
|
2661
|
+
this.showError('Reset token is missing. Please use the link from your email.');
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
try {
|
|
2665
|
+
// Show loading state
|
|
2666
|
+
submitBtn.disabled = true;
|
|
2667
|
+
btnText?.classList.add('hidden');
|
|
2668
|
+
btnLoader?.classList.remove('hidden');
|
|
2669
|
+
// Call reset password API
|
|
2670
|
+
await this.client.resetPassword({
|
|
2671
|
+
token: this.resetToken,
|
|
2672
|
+
new_password: newPassword,
|
|
2673
|
+
});
|
|
2674
|
+
// Clean up URL (remove verify_token from query string)
|
|
2675
|
+
const url = new URL(window.location.href);
|
|
2676
|
+
url.searchParams.delete('verify_token');
|
|
2677
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
2678
|
+
// Clear reset token
|
|
2679
|
+
this.resetToken = null;
|
|
2680
|
+
// Show success message
|
|
2681
|
+
this.showMessage('Password Reset Successful', 'Your password has been updated successfully. You can now log in with your new password.');
|
|
2682
|
+
}
|
|
2683
|
+
catch (error) {
|
|
2684
|
+
// Handle error
|
|
2685
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to reset password';
|
|
2686
|
+
this.showError(errorMessage);
|
|
2687
|
+
if (this.options.onError) {
|
|
2688
|
+
this.options.onError(error);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
finally {
|
|
2692
|
+
// Reset loading state
|
|
2693
|
+
submitBtn.disabled = false;
|
|
2694
|
+
btnText?.classList.remove('hidden');
|
|
2695
|
+
btnLoader?.classList.add('hidden');
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2531
2698
|
showMessage(title, message) {
|
|
2532
2699
|
if (!this.modal)
|
|
2533
2700
|
return;
|