@seaverse/auth-sdk 0.2.3 → 0.2.5

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 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` 参数是**完全可选的**:
@@ -502,7 +532,7 @@ SDK支持以下环境:
502
532
 
503
533
  | 环境 | 描述 | BaseURL |
504
534
  |------|------|---------|
505
- | `production` | 生产环境 | `https://api.seaverse.com` |
535
+ | `production` | 生产环境 | `https://account-hub.seaverse.ai` |
506
536
  | `staging` | 测试环境 | `https://api.staging.seaverse.dev` |
507
537
  | `development` | 开发环境 | `https://api.dev.seaverse.dev` |
508
538
  | `local` | 本地环境 | `http://localhost:3000` |
@@ -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
@@ -1043,7 +1043,7 @@ const ENVIRONMENT_CONFIGS = {
1043
1043
  production: {
1044
1044
  name: 'production',
1045
1045
  baseURL: 'https://account-hub.seaverse.ai',
1046
- wsURL: 'wss://api.seaverse.com',
1046
+ wsURL: 'wss://account-hub.seaverse.ai',
1047
1047
  isProduction: true,
1048
1048
  },
1049
1049
  staging: {
@@ -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;