@seaverse/auth-sdk 0.2.0 → 0.2.2

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/dist/index.cjs CHANGED
@@ -1042,7 +1042,7 @@ class AuthFactory {
1042
1042
  const ENVIRONMENT_CONFIGS = {
1043
1043
  production: {
1044
1044
  name: 'production',
1045
- baseURL: 'https://api.seaverse.com',
1045
+ baseURL: 'https://account-hub.seaverse.ai',
1046
1046
  wsURL: 'wss://api.seaverse.com',
1047
1047
  isProduction: true,
1048
1048
  },
@@ -1226,6 +1226,31 @@ class SeaVerseBackendAPIClient {
1226
1226
  ],
1227
1227
  };
1228
1228
  }
1229
+ /**
1230
+ * Set authentication token
1231
+ * Update the client's authentication token (useful after OAuth login)
1232
+ *
1233
+ * @param token - JWT token to set
1234
+ *
1235
+ * @example
1236
+ * // After OAuth login
1237
+ * const token = new URLSearchParams(window.location.search).get('token');
1238
+ * if (token) {
1239
+ * client.setToken(token);
1240
+ * localStorage.setItem('token', token);
1241
+ * }
1242
+ */
1243
+ setToken(token) {
1244
+ const auth = AuthFactory.create({
1245
+ type: 'jwt',
1246
+ credentials: {
1247
+ type: 'jwt',
1248
+ token,
1249
+ },
1250
+ });
1251
+ // Update the httpClient's auth
1252
+ this.httpClient.auth = auth;
1253
+ }
1229
1254
  // ============================================================================
1230
1255
  // Authentication & OAuth APIs
1231
1256
  // ============================================================================
@@ -1373,36 +1398,39 @@ class SeaVerseBackendAPIClient {
1373
1398
  // OAuth APIs
1374
1399
  // ============================================================================
1375
1400
  /**
1376
- * Exchange Google code for token
1377
- * Exchange Google authorization code for JWT token (for app)
1378
- */
1379
- async googleCodeToToken(data, options) {
1401
+ * Google OAuth authorization (Backend Proxy Mode)
1402
+ * Generate OAuth authorization URL for Google login
1403
+ *
1404
+ * @param data - OAuth authorize request (return_url is optional, defaults to window.location.origin)
1405
+ * @param options - Additional axios request options
1406
+ *
1407
+ * @example
1408
+ * // 使用默认 return_url (当前页面)
1409
+ * const { authorize_url } = await client.googleAuthorize();
1410
+ * window.location.href = authorize_url;
1411
+ *
1412
+ * @example
1413
+ * // 自定义 return_url
1414
+ * const { authorize_url } = await client.googleAuthorize({
1415
+ * return_url: 'https://mygame.com/dashboard'
1416
+ * });
1417
+ * window.location.href = authorize_url;
1418
+ */
1419
+ async googleAuthorize(data, options) {
1420
+ // 如果没有传 return_url,使用当前页面地址
1421
+ const return_url = data?.return_url || (typeof window !== 'undefined' ? window.location.origin : '');
1380
1422
  const config = {
1381
1423
  method: 'POST',
1382
- url: `/sdk/v1/auth/google/code2token`,
1383
- data,
1424
+ url: `/sdk/v1/auth/google/authorize`,
1425
+ data: { return_url },
1384
1426
  headers: {
1385
- 'X-Operation-Id': 'googleCodeToToken',
1427
+ 'X-Operation-Id': 'googleAuthorize',
1386
1428
  ...options?.headers,
1387
1429
  },
1388
1430
  ...options,
1389
1431
  };
1390
1432
  const response = await this.httpClient.request(config);
1391
- // ✅ API返回格式: { data: { token, tempUserId, ... }, success: true }
1392
- const apiData = response.data?.data || response.data;
1393
- if (!apiData || !apiData.token) {
1394
- throw new Error('No token in API response');
1395
- }
1396
- return {
1397
- token: apiData.token,
1398
- user: {
1399
- id: apiData.tempUserId || apiData.userId || apiData.id,
1400
- email: apiData.email,
1401
- username: apiData.username,
1402
- ...(apiData.provider && { provider: apiData.provider }),
1403
- ...(apiData.requiresInvitationCode !== undefined && { requiresInvitationCode: apiData.requiresInvitationCode }),
1404
- }
1405
- };
1433
+ return response.data;
1406
1434
  }
1407
1435
  /**
1408
1436
  * Unlink Google account
@@ -1422,35 +1450,27 @@ class SeaVerseBackendAPIClient {
1422
1450
  return response.data;
1423
1451
  }
1424
1452
  /**
1425
- * Exchange Discord code for token
1453
+ * Discord OAuth authorization (Backend Proxy Mode)
1454
+ * Generate OAuth authorization URL for Discord login
1455
+ *
1456
+ * @param data - OAuth authorize request (return_url is optional, defaults to window.location.origin)
1457
+ * @param options - Additional axios request options
1426
1458
  */
1427
- async discordCodeToToken(data, options) {
1459
+ async discordAuthorize(data, options) {
1460
+ // 如果没有传 return_url,使用当前页面地址
1461
+ const return_url = data?.return_url || (typeof window !== 'undefined' ? window.location.origin : '');
1428
1462
  const config = {
1429
1463
  method: 'POST',
1430
- url: `/sdk/v1/auth/discord/code2token`,
1431
- data,
1464
+ url: `/sdk/v1/auth/discord/authorize`,
1465
+ data: { return_url },
1432
1466
  headers: {
1433
- 'X-Operation-Id': 'discordCodeToToken',
1467
+ 'X-Operation-Id': 'discordAuthorize',
1434
1468
  ...options?.headers,
1435
1469
  },
1436
1470
  ...options,
1437
1471
  };
1438
1472
  const response = await this.httpClient.request(config);
1439
- // ✅ API返回格式: { data: { token, tempUserId, ... }, success: true }
1440
- const apiData = response.data?.data || response.data;
1441
- if (!apiData || !apiData.token) {
1442
- throw new Error('No token in API response');
1443
- }
1444
- return {
1445
- token: apiData.token,
1446
- user: {
1447
- id: apiData.tempUserId || apiData.userId || apiData.id,
1448
- email: apiData.email,
1449
- username: apiData.username,
1450
- ...(apiData.provider && { provider: apiData.provider }),
1451
- ...(apiData.requiresInvitationCode !== undefined && { requiresInvitationCode: apiData.requiresInvitationCode }),
1452
- }
1453
- };
1473
+ return response.data;
1454
1474
  }
1455
1475
  /**
1456
1476
  * Unlink Discord account
@@ -1469,46 +1489,27 @@ class SeaVerseBackendAPIClient {
1469
1489
  return response.data;
1470
1490
  }
1471
1491
  /**
1472
- * Exchange GitHub code for token
1492
+ * GitHub OAuth authorization (Backend Proxy Mode)
1493
+ * Generate OAuth authorization URL for GitHub login
1494
+ *
1495
+ * @param data - OAuth authorize request (return_url is optional, defaults to window.location.origin)
1496
+ * @param options - Additional axios request options
1473
1497
  */
1474
- async githubCodeToToken(data, options) {
1498
+ async githubAuthorize(data, options) {
1499
+ // 如果没有传 return_url,使用当前页面地址
1500
+ const return_url = data?.return_url || (typeof window !== 'undefined' ? window.location.origin : '');
1475
1501
  const config = {
1476
1502
  method: 'POST',
1477
- url: `/sdk/v1/auth/github/code2token`,
1478
- data,
1503
+ url: `/sdk/v1/auth/github/authorize`,
1504
+ data: { return_url },
1479
1505
  headers: {
1480
- 'X-Operation-Id': 'githubCodeToToken',
1506
+ 'X-Operation-Id': 'githubAuthorize',
1481
1507
  ...options?.headers,
1482
1508
  },
1483
1509
  ...options,
1484
1510
  };
1485
- console.log('🔐 [SDK Client] Calling githubCodeToToken with config:', config);
1486
1511
  const response = await this.httpClient.request(config);
1487
- console.log('📡 [SDK Client] Raw response.data:', response.data);
1488
- // ✅ API返回格式: { data: { token, tempUserId, email, ... }, success: true }
1489
- // 需要访问 response.data.data 而不是 response.data
1490
- const apiData = response.data?.data || response.data;
1491
- console.log('📡 [SDK Client] Extracted apiData:', apiData);
1492
- console.log('📡 [SDK Client] apiData.token:', apiData?.token);
1493
- if (!apiData || !apiData.token) {
1494
- console.error('❌ [SDK Client] No token found!');
1495
- console.error('❌ [SDK Client] response.data:', JSON.stringify(response.data, null, 2));
1496
- throw new Error('No token in API response');
1497
- }
1498
- // 构造符合SDK期望的响应格式
1499
- const normalizedResponse = {
1500
- token: apiData.token,
1501
- user: {
1502
- id: apiData.tempUserId || apiData.userId || apiData.id,
1503
- email: apiData.email,
1504
- username: apiData.username,
1505
- // 保留额外的字段
1506
- ...(apiData.provider && { provider: apiData.provider }),
1507
- ...(apiData.requiresInvitationCode !== undefined && { requiresInvitationCode: apiData.requiresInvitationCode }),
1508
- }
1509
- };
1510
- console.log('✅ [SDK Client] Normalized response:', normalizedResponse);
1511
- return normalizedResponse;
1512
+ return response.data;
1512
1513
  }
1513
1514
  /**
1514
1515
  * Unlink GitHub account
@@ -1962,9 +1963,9 @@ class AuthModal {
1962
1963
  submitBtn.appendChild(btnText);
1963
1964
  submitBtn.appendChild(btnLoader);
1964
1965
  form.appendChild(submitBtn);
1965
- // OAuth buttons - only show if configured
1966
- const hasOAuth = this.options.oauthConfig &&
1967
- (this.options.oauthConfig.google || this.options.oauthConfig.discord || this.options.oauthConfig.github);
1966
+ // OAuth buttons - only show if enabled
1967
+ const hasOAuth = this.options.enableOAuth &&
1968
+ (this.options.enableOAuth.google || this.options.enableOAuth.discord || this.options.enableOAuth.github);
1968
1969
  if (hasOAuth) {
1969
1970
  // Divider
1970
1971
  const divider = document.createElement('div');
@@ -1974,13 +1975,13 @@ class AuthModal {
1974
1975
  // Social buttons grid (Google + GitHub)
1975
1976
  const socialGrid = document.createElement('div');
1976
1977
  socialGrid.className = 'social-buttons-grid';
1977
- // Google button - only if configured
1978
- if (this.options.oauthConfig?.google) {
1978
+ // Google button - only if enabled
1979
+ if (this.options.enableOAuth?.google) {
1979
1980
  const googleBtn = this.createSocialButton('google', 'Google', 'login');
1980
1981
  socialGrid.appendChild(googleBtn);
1981
1982
  }
1982
1983
  // GitHub button - only if configured
1983
- if (this.options.oauthConfig?.github) {
1984
+ if (this.options.enableOAuth?.github) {
1984
1985
  const githubBtn = this.createSocialButton('github', 'Github', 'login');
1985
1986
  socialGrid.appendChild(githubBtn);
1986
1987
  }
@@ -1989,7 +1990,7 @@ class AuthModal {
1989
1990
  form.appendChild(socialGrid);
1990
1991
  }
1991
1992
  // Discord button (full width) - only if configured
1992
- if (this.options.oauthConfig?.discord) {
1993
+ if (this.options.enableOAuth?.discord) {
1993
1994
  const discordBtn = this.createSocialButton('discord', 'Discord', 'login', true);
1994
1995
  form.appendChild(discordBtn);
1995
1996
  }
@@ -2066,24 +2067,38 @@ class AuthModal {
2066
2067
  submitBtn.appendChild(btnText);
2067
2068
  submitBtn.appendChild(btnLoader);
2068
2069
  form.appendChild(submitBtn);
2069
- // Divider
2070
- const divider = document.createElement('div');
2071
- divider.className = 'divider';
2072
- divider.textContent = 'OR SIGN UP WITH';
2073
- form.appendChild(divider);
2074
- // Social buttons grid (Google + GitHub)
2075
- const socialGrid = document.createElement('div');
2076
- socialGrid.className = 'social-buttons-grid';
2077
- // Google button
2078
- const googleBtn = this.createSocialButton('google', 'Google', 'signup');
2079
- socialGrid.appendChild(googleBtn);
2080
- // GitHub button
2081
- const githubBtn = this.createSocialButton('github', 'Github', 'signup');
2082
- socialGrid.appendChild(githubBtn);
2083
- form.appendChild(socialGrid);
2084
- // Discord button (full width)
2085
- const discordBtn = this.createSocialButton('discord', 'Discord', 'signup', true);
2086
- form.appendChild(discordBtn);
2070
+ // OAuth buttons - only show if enabled
2071
+ const hasOAuth = this.options.enableOAuth &&
2072
+ (this.options.enableOAuth.google || this.options.enableOAuth.discord || this.options.enableOAuth.github);
2073
+ if (hasOAuth) {
2074
+ // Divider
2075
+ const divider = document.createElement('div');
2076
+ divider.className = 'divider';
2077
+ divider.textContent = 'OR SIGN UP WITH';
2078
+ form.appendChild(divider);
2079
+ // Social buttons grid (Google + GitHub)
2080
+ const socialGrid = document.createElement('div');
2081
+ socialGrid.className = 'social-buttons-grid';
2082
+ // Google button - only if enabled
2083
+ if (this.options.enableOAuth?.google) {
2084
+ const googleBtn = this.createSocialButton('google', 'Google', 'signup');
2085
+ socialGrid.appendChild(googleBtn);
2086
+ }
2087
+ // GitHub button - only if configured
2088
+ if (this.options.enableOAuth?.github) {
2089
+ const githubBtn = this.createSocialButton('github', 'Github', 'signup');
2090
+ socialGrid.appendChild(githubBtn);
2091
+ }
2092
+ // Only add grid if it has buttons
2093
+ if (socialGrid.children.length > 0) {
2094
+ form.appendChild(socialGrid);
2095
+ }
2096
+ // Discord button (full width) - only if configured
2097
+ if (this.options.enableOAuth?.discord) {
2098
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'signup', true);
2099
+ form.appendChild(discordBtn);
2100
+ }
2101
+ }
2087
2102
  container.appendChild(form);
2088
2103
  return container;
2089
2104
  }
@@ -2499,134 +2514,74 @@ class AuthModal {
2499
2514
  // OAuth Methods
2500
2515
  // ============================================================================
2501
2516
  /**
2502
- * Start OAuth flow for a given provider
2503
- */
2504
- startOAuthFlow(provider) {
2505
- const config = this.options.oauthConfig?.[provider];
2506
- if (!config) {
2507
- this.showError(`${provider} OAuth is not configured`);
2508
- return;
2509
- }
2510
- // Store the current state (to verify callback)
2511
- // ✅ 使用 localStorage 而不是 sessionStorage,避免跨页面跳转时丢失
2512
- const state = this.generateRandomState();
2513
- localStorage.setItem('oauth_state', state);
2514
- localStorage.setItem('oauth_provider', provider);
2515
- // Build authorization URL
2516
- const authUrl = this.buildAuthUrl(provider, config, state);
2517
- // Redirect to OAuth provider
2518
- window.location.href = authUrl;
2519
- }
2520
- /**
2521
- * Generate random state for CSRF protection
2522
- */
2523
- generateRandomState() {
2524
- const array = new Uint8Array(32);
2525
- crypto.getRandomValues(array);
2526
- return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
2527
- }
2528
- /**
2529
- * Build authorization URL for each provider
2517
+ * Start OAuth flow for a given provider (Backend Proxy Mode)
2518
+ *
2519
+ * This method calls the backend /authorize endpoint to get the OAuth URL,
2520
+ * then redirects the user. The backend handles:
2521
+ * - State token generation and storage
2522
+ * - OAuth callback processing
2523
+ * - JWT token generation
2524
+ * - Redirecting back to returnUrl with token
2530
2525
  */
2531
- buildAuthUrl(provider, config, state) {
2532
- const params = new URLSearchParams({
2533
- client_id: config.clientId,
2534
- redirect_uri: config.redirectUri,
2535
- state,
2536
- response_type: 'code',
2537
- });
2538
- // Provider-specific configurations
2539
- switch (provider) {
2540
- case 'google':
2541
- params.append('scope', config.scope || 'openid email profile');
2542
- params.append('access_type', 'offline');
2543
- params.append('prompt', 'consent');
2544
- return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
2545
- case 'discord':
2546
- params.append('scope', config.scope || 'identify email');
2547
- return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
2548
- case 'github':
2549
- params.append('scope', config.scope || 'read:user user:email');
2550
- return `https://github.com/login/oauth/authorize?${params.toString()}`;
2551
- default:
2552
- throw new Error(`Unknown provider: ${provider}`);
2553
- }
2554
- }
2555
- /**
2556
- * Handle OAuth callback
2557
- * Call this method from your redirect page with the code and state from URL
2558
- */
2559
- async handleOAuthCallback(code, state) {
2526
+ async startOAuthFlow(provider) {
2560
2527
  try {
2561
- // Verify state
2562
- // 使用 localStorage 而不是 sessionStorage
2563
- const storedState = localStorage.getItem('oauth_state');
2564
- const provider = localStorage.getItem('oauth_provider');
2565
- if (!storedState || storedState !== state) {
2566
- throw new Error('Invalid state parameter - possible CSRF attack');
2567
- }
2568
- if (!provider) {
2569
- throw new Error('No provider stored in session');
2570
- }
2571
- // Clear stored state
2572
- localStorage.removeItem('oauth_state');
2573
- localStorage.removeItem('oauth_provider');
2574
- // Exchange code for token using the appropriate SDK method
2575
- let response;
2528
+ // Get the return URL (where user should be redirected after OAuth)
2529
+ const return_url = this.options.returnUrl || window.location.origin;
2530
+ // Call backend to get OAuth authorization URL
2531
+ let authorizeUrl;
2576
2532
  switch (provider) {
2577
2533
  case 'google':
2578
- response = await this.client.googleCodeToToken({ code });
2534
+ const googleResult = await this.client.googleAuthorize({ return_url });
2535
+ authorizeUrl = googleResult.authorize_url;
2579
2536
  break;
2580
2537
  case 'discord':
2581
- response = await this.client.discordCodeToToken({ code });
2538
+ const discordResult = await this.client.discordAuthorize({ return_url });
2539
+ authorizeUrl = discordResult.authorize_url;
2582
2540
  break;
2583
2541
  case 'github':
2584
- response = await this.client.githubCodeToToken({ code });
2542
+ const githubResult = await this.client.githubAuthorize({ return_url });
2543
+ authorizeUrl = githubResult.authorize_url;
2585
2544
  break;
2586
2545
  default:
2587
2546
  throw new Error(`Unknown provider: ${provider}`);
2588
2547
  }
2589
- // Handle success
2590
- if (response.token) {
2591
- if (this.options.onLoginSuccess) {
2592
- this.options.onLoginSuccess(response.token, response.user);
2593
- }
2594
- return {
2595
- success: true,
2596
- token: response.token,
2597
- user: response.user,
2598
- };
2599
- }
2600
- else {
2601
- throw new Error('No token received from server');
2602
- }
2548
+ // Redirect to OAuth provider
2549
+ window.location.href = authorizeUrl;
2603
2550
  }
2604
2551
  catch (error) {
2605
- const err = error instanceof Error ? error : new Error('OAuth authentication failed');
2552
+ const err = error instanceof Error ? error : new Error(`${provider} OAuth failed`);
2553
+ this.showError(err.message);
2606
2554
  if (this.options.onError) {
2607
2555
  this.options.onError(err);
2608
2556
  }
2609
- return {
2610
- success: false,
2611
- error: err,
2612
- };
2613
2557
  }
2614
2558
  }
2615
2559
  /**
2616
- * Check if current page is an OAuth callback and handle it automatically
2560
+ * Handle OAuth callback (Backend Proxy Mode)
2561
+ *
2562
+ * In Backend Proxy Mode, the backend redirects to returnUrl with token in query param.
2563
+ * This static method checks if current URL has a token and processes it.
2617
2564
  */
2618
- static async handleOAuthCallbackFromUrl(client, options) {
2565
+ static handleOAuthCallback(options) {
2619
2566
  const urlParams = new URLSearchParams(window.location.search);
2620
- const code = urlParams.get('code');
2621
- const state = urlParams.get('state');
2622
- if (!code || !state) {
2567
+ const token = urlParams.get('token');
2568
+ if (!token) {
2623
2569
  return null; // Not an OAuth callback
2624
2570
  }
2625
- const modal = new AuthModal({
2626
- client,
2627
- ...options,
2628
- });
2629
- return await modal.handleOAuthCallback(code, state);
2571
+ // Clean up URL (remove token from query string)
2572
+ const url = new URL(window.location.href);
2573
+ url.searchParams.delete('token');
2574
+ window.history.replaceState({}, document.title, url.toString());
2575
+ // Automatically set token in client for subsequent authenticated requests
2576
+ options.client.setToken(token);
2577
+ // Call success callback
2578
+ if (options.onLoginSuccess) {
2579
+ options.onLoginSuccess(token, {}); // User info can be fetched separately if needed
2580
+ }
2581
+ return {
2582
+ success: true,
2583
+ token,
2584
+ };
2630
2585
  }
2631
2586
  }
2632
2587
  /**