@seaverse/auth-sdk 0.2.2 → 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 +256 -2
- package/dist/index.cjs +213 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +48 -6
- package/dist/index.js +214 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,8 +94,17 @@ 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
|
|
|
100
|
+
// 检查注册结果
|
|
101
|
+
if (registerResult.success) {
|
|
102
|
+
console.log('注册成功:', registerResult);
|
|
103
|
+
} else if (registerResult.code === 'ACCOUNT_EXISTS') {
|
|
104
|
+
console.log('账户已存在,请直接登录');
|
|
105
|
+
console.log('错误详情:', registerResult.details);
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
// 登录
|
|
100
109
|
const loginResult = await client.login({
|
|
101
110
|
email: 'user@example.com',
|
|
@@ -117,6 +126,7 @@ await client.logout();
|
|
|
117
126
|
// 忘记密码
|
|
118
127
|
await client.forgotPassword({
|
|
119
128
|
email: 'user@example.com',
|
|
129
|
+
frontend_url: 'https://mygame.com/', // 可选,默认为 window.location.origin
|
|
120
130
|
});
|
|
121
131
|
|
|
122
132
|
// 重置密码
|
|
@@ -262,6 +272,34 @@ authModal.hide();
|
|
|
262
272
|
authModal.destroy();
|
|
263
273
|
```
|
|
264
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
|
+
|
|
265
303
|
#### OAuth 配置说明
|
|
266
304
|
|
|
267
305
|
`enableOAuth` 参数是**完全可选的**:
|
|
@@ -511,11 +549,11 @@ SDK支持以下环境:
|
|
|
511
549
|
|
|
512
550
|
| 方法 | 参数 | 返回值 | 说明 |
|
|
513
551
|
|------|------|--------|------|
|
|
514
|
-
| `register()` | `{ email, password, username?, invitation_code? }` | `RegisterResponse` |
|
|
552
|
+
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` | 注册新用户,frontend_url 为邮箱验证链接的前端URL,默认为 window.location.origin |
|
|
515
553
|
| `login()` | `{ email, password }` | `LoginResponse` | 用户登录 |
|
|
516
554
|
| `getCurrentUser()` | - | `User` | 获取当前用户 |
|
|
517
555
|
| `logout()` | - | `SuccessResponse` | 登出 |
|
|
518
|
-
| `forgotPassword()` | `{ email }` | `SuccessResponse` |
|
|
556
|
+
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` | 忘记密码,frontend_url 默认为 window.location.origin |
|
|
519
557
|
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` | 重置密码 |
|
|
520
558
|
| `setToken()` | `token: string` | `void` | 设置认证 token(OAuth 登录后使用) |
|
|
521
559
|
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` | 获取API Token |
|
|
@@ -560,6 +598,222 @@ SDK支持以下环境:
|
|
|
560
598
|
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` | 获取对话状态 |
|
|
561
599
|
| `getSpeechToken()` | - | `SpeechTokenResponse` | 获取语音Token |
|
|
562
600
|
|
|
601
|
+
## 错误处理
|
|
602
|
+
|
|
603
|
+
### 错误响应格式
|
|
604
|
+
|
|
605
|
+
所有 API 错误都遵循统一的响应格式:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
interface ApiError {
|
|
609
|
+
success: false; // 错误时始终为 false
|
|
610
|
+
error: string; // 人类可读的错误消息
|
|
611
|
+
code?: string; // 机器可读的错误码
|
|
612
|
+
details?: any; // 额外的错误详情
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### 错误码
|
|
617
|
+
|
|
618
|
+
SDK 提供了标准的错误码枚举:
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
622
|
+
|
|
623
|
+
// 账户相关错误
|
|
624
|
+
ErrorCode.ACCOUNT_EXISTS // 账户已存在
|
|
625
|
+
ErrorCode.ACCOUNT_NOT_FOUND // 账户不存在
|
|
626
|
+
ErrorCode.ACCOUNT_SUSPENDED // 账户已被暂停
|
|
627
|
+
ErrorCode.INVALID_CREDENTIALS // 登录凭证无效
|
|
628
|
+
ErrorCode.EMAIL_NOT_VERIFIED // 邮箱未验证
|
|
629
|
+
|
|
630
|
+
// 验证相关错误
|
|
631
|
+
ErrorCode.INVALID_EMAIL // 无效的邮箱地址
|
|
632
|
+
ErrorCode.INVALID_PASSWORD // 无效的密码
|
|
633
|
+
ErrorCode.PASSWORD_TOO_WEAK // 密码强度不够
|
|
634
|
+
|
|
635
|
+
// 邀请码相关错误
|
|
636
|
+
ErrorCode.INVALID_INVITATION_CODE // 无效的邀请码
|
|
637
|
+
ErrorCode.INVITATION_REQUIRED // 需要邀请码
|
|
638
|
+
|
|
639
|
+
// Token 相关错误
|
|
640
|
+
ErrorCode.INVALID_TOKEN // 无效的 token
|
|
641
|
+
ErrorCode.TOKEN_EXPIRED // Token 已过期
|
|
642
|
+
|
|
643
|
+
// 内部错误
|
|
644
|
+
ErrorCode.INTERNAL_ERROR // 服务器内部错误
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### 错误处理最佳实践
|
|
648
|
+
|
|
649
|
+
#### 1. 注册时处理重复用户
|
|
650
|
+
|
|
651
|
+
由于后端在账户已存在时返回 200 OK(成功响应),所以前端不会抛出异常,而是在响应体中包含错误信息。
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
655
|
+
|
|
656
|
+
const result = await client.register({
|
|
657
|
+
email: 'user@example.com',
|
|
658
|
+
password: 'password123',
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// 检查响应中的 success 字段
|
|
662
|
+
if (result.success) {
|
|
663
|
+
console.log('注册成功:', result);
|
|
664
|
+
// 进行后续操作,如自动登录
|
|
665
|
+
} else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
|
|
666
|
+
// 账户已存在,提示用户
|
|
667
|
+
const { email, app_id } = result.details;
|
|
668
|
+
console.log(`账户 ${email} 已存在于应用 ${app_id} 中`);
|
|
669
|
+
|
|
670
|
+
// 显示友好的提示信息
|
|
671
|
+
alert('This email is already registered. Please login instead.');
|
|
672
|
+
|
|
673
|
+
// 或者引导用户去登录
|
|
674
|
+
showLoginModal();
|
|
675
|
+
} else {
|
|
676
|
+
// 处理其他错误
|
|
677
|
+
console.error('注册失败:', result.error);
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
#### 2. 登录时处理各种错误
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
try {
|
|
685
|
+
const result = await client.login({
|
|
686
|
+
email: 'user@example.com',
|
|
687
|
+
password: 'wrong-password',
|
|
688
|
+
});
|
|
689
|
+
console.log('登录成功:', result);
|
|
690
|
+
} catch (error) {
|
|
691
|
+
const errorCode = error.response?.data?.code;
|
|
692
|
+
|
|
693
|
+
switch (errorCode) {
|
|
694
|
+
case ErrorCode.INVALID_CREDENTIALS:
|
|
695
|
+
showError('用户名或密码错误');
|
|
696
|
+
break;
|
|
697
|
+
case ErrorCode.EMAIL_NOT_VERIFIED:
|
|
698
|
+
showError('请先验证您的邮箱');
|
|
699
|
+
showResendVerificationButton();
|
|
700
|
+
break;
|
|
701
|
+
case ErrorCode.ACCOUNT_SUSPENDED:
|
|
702
|
+
showError('您的账户已被暂停,请联系管理员');
|
|
703
|
+
break;
|
|
704
|
+
default:
|
|
705
|
+
showError('登录失败,请稍后重试');
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
#### 3. 通用错误处理函数
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
714
|
+
|
|
715
|
+
function handleApiError(error: any) {
|
|
716
|
+
const apiError = error.response?.data;
|
|
717
|
+
|
|
718
|
+
if (!apiError) {
|
|
719
|
+
// 网络错误或其他未知错误
|
|
720
|
+
return {
|
|
721
|
+
title: '网络错误',
|
|
722
|
+
message: '请检查您的网络连接',
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// 根据错误码返回用户友好的消息
|
|
727
|
+
const errorMessages: Record<string, { title: string; message: string }> = {
|
|
728
|
+
[ErrorCode.ACCOUNT_EXISTS]: {
|
|
729
|
+
title: '账户已存在',
|
|
730
|
+
message: '该邮箱已注册,请直接登录',
|
|
731
|
+
},
|
|
732
|
+
[ErrorCode.INVALID_CREDENTIALS]: {
|
|
733
|
+
title: '登录失败',
|
|
734
|
+
message: '用户名或密码错误',
|
|
735
|
+
},
|
|
736
|
+
[ErrorCode.EMAIL_NOT_VERIFIED]: {
|
|
737
|
+
title: '邮箱未验证',
|
|
738
|
+
message: '请先验证您的邮箱地址',
|
|
739
|
+
},
|
|
740
|
+
[ErrorCode.INVALID_INVITATION_CODE]: {
|
|
741
|
+
title: '邀请码无效',
|
|
742
|
+
message: '请检查您的邀请码是否正确',
|
|
743
|
+
},
|
|
744
|
+
[ErrorCode.TOKEN_EXPIRED]: {
|
|
745
|
+
title: '登录已过期',
|
|
746
|
+
message: '请重新登录',
|
|
747
|
+
},
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
return errorMessages[apiError.code] || {
|
|
751
|
+
title: '操作失败',
|
|
752
|
+
message: apiError.error || '发生未知错误',
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// 使用示例
|
|
757
|
+
try {
|
|
758
|
+
await client.register({ email, password });
|
|
759
|
+
} catch (error) {
|
|
760
|
+
const { title, message } = handleApiError(error);
|
|
761
|
+
showNotification(title, message);
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
#### 4. TypeScript 类型安全的错误处理
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
import { ErrorCode, models } from '@seaverse/auth-sdk';
|
|
769
|
+
|
|
770
|
+
async function safeRegister(email: string, password: string) {
|
|
771
|
+
// Register API now returns 200 OK for both success and account exists cases
|
|
772
|
+
const result = await client.register({ email, password });
|
|
773
|
+
|
|
774
|
+
if (result.success) {
|
|
775
|
+
return {
|
|
776
|
+
success: true,
|
|
777
|
+
data: result,
|
|
778
|
+
};
|
|
779
|
+
} else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
|
|
780
|
+
// TypeScript knows the type of details
|
|
781
|
+
const details = result.details as models.AccountExistsErrorDetails;
|
|
782
|
+
return {
|
|
783
|
+
success: false,
|
|
784
|
+
error: 'ACCOUNT_EXISTS',
|
|
785
|
+
email: details.email,
|
|
786
|
+
appId: details.app_id,
|
|
787
|
+
};
|
|
788
|
+
} else {
|
|
789
|
+
return {
|
|
790
|
+
success: false,
|
|
791
|
+
error: result.error || 'Unknown error',
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Usage
|
|
797
|
+
const registerResult = await safeRegister('user@example.com', 'password123');
|
|
798
|
+
if (registerResult.success) {
|
|
799
|
+
console.log('Registration successful');
|
|
800
|
+
} else if (registerResult.error === 'ACCOUNT_EXISTS') {
|
|
801
|
+
console.log(`Account already exists: ${registerResult.email}`);
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### HTTP 状态码对照
|
|
806
|
+
|
|
807
|
+
| HTTP状态码 | 错误码示例 | 说明 |
|
|
808
|
+
|-----------|----------|------|
|
|
809
|
+
| 200 OK | `ACCOUNT_EXISTS` | 账户已存在(业务错误但返回成功响应) |
|
|
810
|
+
| 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` | 请求参数无效 |
|
|
811
|
+
| 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` | 认证失败 |
|
|
812
|
+
| 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` | 权限不足 |
|
|
813
|
+
| 500 Internal Server Error | `INTERNAL_ERROR` | 服务器内部错误 |
|
|
814
|
+
|
|
815
|
+
**注意**:对于注册接口,账户已存在的情况会返回 200 OK,但在响应体中 `success: false` 和 `code: "ACCOUNT_EXISTS"`。这样设计是为了前端能够更容易地处理这种常见的业务场景。
|
|
816
|
+
|
|
563
817
|
## 类型定义
|
|
564
818
|
|
|
565
819
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -1136,9 +1136,37 @@ function resolveBaseURL(options) {
|
|
|
1136
1136
|
* Type definitions for SeaVerse Dispatcher API
|
|
1137
1137
|
* Generated from auth.yaml OpenAPI specification
|
|
1138
1138
|
*/
|
|
1139
|
+
// ============================================================================
|
|
1140
|
+
// Error Types
|
|
1141
|
+
// ============================================================================
|
|
1142
|
+
/**
|
|
1143
|
+
* Error codes for authentication system
|
|
1144
|
+
*/
|
|
1145
|
+
exports.ErrorCode = void 0;
|
|
1146
|
+
(function (ErrorCode) {
|
|
1147
|
+
// Account errors
|
|
1148
|
+
ErrorCode["ACCOUNT_EXISTS"] = "ACCOUNT_EXISTS";
|
|
1149
|
+
ErrorCode["ACCOUNT_NOT_FOUND"] = "ACCOUNT_NOT_FOUND";
|
|
1150
|
+
ErrorCode["ACCOUNT_SUSPENDED"] = "ACCOUNT_SUSPENDED";
|
|
1151
|
+
ErrorCode["INVALID_CREDENTIALS"] = "INVALID_CREDENTIALS";
|
|
1152
|
+
ErrorCode["EMAIL_NOT_VERIFIED"] = "EMAIL_NOT_VERIFIED";
|
|
1153
|
+
// Validation errors
|
|
1154
|
+
ErrorCode["INVALID_EMAIL"] = "INVALID_EMAIL";
|
|
1155
|
+
ErrorCode["INVALID_PASSWORD"] = "INVALID_PASSWORD";
|
|
1156
|
+
ErrorCode["PASSWORD_TOO_WEAK"] = "PASSWORD_TOO_WEAK";
|
|
1157
|
+
// Invitation errors
|
|
1158
|
+
ErrorCode["INVALID_INVITATION_CODE"] = "INVALID_INVITATION_CODE";
|
|
1159
|
+
ErrorCode["INVITATION_REQUIRED"] = "INVITATION_REQUIRED";
|
|
1160
|
+
// Token errors
|
|
1161
|
+
ErrorCode["INVALID_TOKEN"] = "INVALID_TOKEN";
|
|
1162
|
+
ErrorCode["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
|
|
1163
|
+
// Internal errors
|
|
1164
|
+
ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
1165
|
+
})(exports.ErrorCode || (exports.ErrorCode = {}));
|
|
1139
1166
|
|
|
1140
1167
|
var models = /*#__PURE__*/Object.freeze({
|
|
1141
|
-
__proto__: null
|
|
1168
|
+
__proto__: null,
|
|
1169
|
+
get ErrorCode () { return exports.ErrorCode; }
|
|
1142
1170
|
});
|
|
1143
1171
|
|
|
1144
1172
|
/**
|
|
@@ -1276,10 +1304,12 @@ class SeaVerseBackendAPIClient {
|
|
|
1276
1304
|
* Register a new user with email verification
|
|
1277
1305
|
*/
|
|
1278
1306
|
async register(data, options) {
|
|
1307
|
+
// 如果没有传 frontend_url,使用当前页面地址
|
|
1308
|
+
const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
1279
1309
|
const config = {
|
|
1280
1310
|
method: 'POST',
|
|
1281
1311
|
url: `/sdk/v1/auth/register`,
|
|
1282
|
-
data,
|
|
1312
|
+
data: { ...data, frontend_url },
|
|
1283
1313
|
headers: {
|
|
1284
1314
|
'X-Operation-Id': 'register',
|
|
1285
1315
|
...options?.headers,
|
|
@@ -1346,10 +1376,12 @@ class SeaVerseBackendAPIClient {
|
|
|
1346
1376
|
* Send password reset email
|
|
1347
1377
|
*/
|
|
1348
1378
|
async forgotPassword(data, options) {
|
|
1379
|
+
// 如果没有传 frontend_url,使用当前页面地址
|
|
1380
|
+
const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
1349
1381
|
const config = {
|
|
1350
1382
|
method: 'POST',
|
|
1351
1383
|
url: `/sdk/v1/auth/forgot-password`,
|
|
1352
|
-
data,
|
|
1384
|
+
data: { ...data, frontend_url },
|
|
1353
1385
|
headers: {
|
|
1354
1386
|
'X-Operation-Id': 'forgotPassword',
|
|
1355
1387
|
...options?.headers,
|
|
@@ -1786,8 +1818,11 @@ class AuthModal {
|
|
|
1786
1818
|
constructor(options) {
|
|
1787
1819
|
this.modal = null;
|
|
1788
1820
|
this.currentView = 'login';
|
|
1821
|
+
this.resetToken = null;
|
|
1789
1822
|
this.client = options.client;
|
|
1790
1823
|
this.options = options;
|
|
1824
|
+
// Auto-detect reset token in URL
|
|
1825
|
+
this.checkForResetToken();
|
|
1791
1826
|
}
|
|
1792
1827
|
/**
|
|
1793
1828
|
* Show the authentication modal
|
|
@@ -1895,6 +1930,9 @@ class AuthModal {
|
|
|
1895
1930
|
// Forgot password form
|
|
1896
1931
|
const forgotForm = this.createForgotPasswordForm();
|
|
1897
1932
|
rightPanel.appendChild(forgotForm);
|
|
1933
|
+
// Reset password form
|
|
1934
|
+
const resetForm = this.createResetPasswordForm();
|
|
1935
|
+
rightPanel.appendChild(resetForm);
|
|
1898
1936
|
// Success message
|
|
1899
1937
|
const successMessage = this.createSuccessMessage();
|
|
1900
1938
|
rightPanel.appendChild(successMessage);
|
|
@@ -2148,6 +2186,86 @@ class AuthModal {
|
|
|
2148
2186
|
container.appendChild(footer);
|
|
2149
2187
|
return container;
|
|
2150
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
|
+
}
|
|
2151
2269
|
createSuccessMessage() {
|
|
2152
2270
|
const container = document.createElement('div');
|
|
2153
2271
|
container.id = 'authMessage';
|
|
@@ -2237,6 +2355,20 @@ class AuthModal {
|
|
|
2237
2355
|
button.appendChild(span);
|
|
2238
2356
|
return button;
|
|
2239
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
|
+
}
|
|
2240
2372
|
bindEventListeners() {
|
|
2241
2373
|
if (!this.modal)
|
|
2242
2374
|
return;
|
|
@@ -2267,6 +2399,11 @@ class AuthModal {
|
|
|
2267
2399
|
e.preventDefault();
|
|
2268
2400
|
this.switchView('login');
|
|
2269
2401
|
});
|
|
2402
|
+
const backToLoginFromReset = this.modal.querySelector('#backToLoginFromReset');
|
|
2403
|
+
backToLoginFromReset?.addEventListener('click', (e) => {
|
|
2404
|
+
e.preventDefault();
|
|
2405
|
+
this.switchView('login');
|
|
2406
|
+
});
|
|
2270
2407
|
// Password toggle
|
|
2271
2408
|
const passwordToggles = this.modal.querySelectorAll('.toggle-password');
|
|
2272
2409
|
passwordToggles.forEach((toggle) => {
|
|
@@ -2297,6 +2434,8 @@ class AuthModal {
|
|
|
2297
2434
|
signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
|
|
2298
2435
|
const forgotForm = this.modal.querySelector('#forgotPasswordFormElement');
|
|
2299
2436
|
forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
|
|
2437
|
+
const resetPasswordForm = this.modal.querySelector('#resetPasswordFormElement');
|
|
2438
|
+
resetPasswordForm?.addEventListener('submit', (e) => this.handleResetPassword(e));
|
|
2300
2439
|
// OAuth social login buttons
|
|
2301
2440
|
this.bindSocialLoginButtons();
|
|
2302
2441
|
}
|
|
@@ -2343,7 +2482,7 @@ class AuthModal {
|
|
|
2343
2482
|
switchView(view) {
|
|
2344
2483
|
if (!this.modal)
|
|
2345
2484
|
return;
|
|
2346
|
-
const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'authMessage'];
|
|
2485
|
+
const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'resetPasswordForm', 'authMessage'];
|
|
2347
2486
|
views.forEach((viewId) => {
|
|
2348
2487
|
const element = this.modal.querySelector(`#${viewId}`);
|
|
2349
2488
|
element?.classList.add('hidden');
|
|
@@ -2352,6 +2491,7 @@ class AuthModal {
|
|
|
2352
2491
|
login: 'loginForm',
|
|
2353
2492
|
signup: 'signupForm',
|
|
2354
2493
|
forgot: 'forgotPasswordForm',
|
|
2494
|
+
'reset-password': 'resetPasswordForm',
|
|
2355
2495
|
message: 'authMessage',
|
|
2356
2496
|
};
|
|
2357
2497
|
const targetView = this.modal.querySelector(`#${viewMap[view]}`);
|
|
@@ -2444,13 +2584,22 @@ class AuthModal {
|
|
|
2444
2584
|
this.showMessage('Account Created', response.message || 'Your account has been created successfully!');
|
|
2445
2585
|
}
|
|
2446
2586
|
}
|
|
2587
|
+
else if (response.code === exports.ErrorCode.ACCOUNT_EXISTS) {
|
|
2588
|
+
// Handle account already exists error
|
|
2589
|
+
this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
|
|
2590
|
+
}
|
|
2447
2591
|
else {
|
|
2448
|
-
throw new Error('Registration failed');
|
|
2592
|
+
throw new Error(response.error || 'Registration failed');
|
|
2449
2593
|
}
|
|
2450
2594
|
}
|
|
2451
2595
|
catch (error) {
|
|
2452
|
-
// Handle
|
|
2453
|
-
|
|
2596
|
+
// Handle HTTP errors
|
|
2597
|
+
if (error.response?.data?.code === exports.ErrorCode.ACCOUNT_EXISTS) {
|
|
2598
|
+
this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
// Handle other errors
|
|
2602
|
+
const errorMessage = error.response?.data?.error || error.message || 'Signup failed';
|
|
2454
2603
|
this.showError(errorMessage);
|
|
2455
2604
|
if (this.options.onError) {
|
|
2456
2605
|
this.options.onError(error);
|
|
@@ -2473,8 +2622,8 @@ class AuthModal {
|
|
|
2473
2622
|
try {
|
|
2474
2623
|
// Show loading state
|
|
2475
2624
|
submitBtn.disabled = true;
|
|
2476
|
-
//
|
|
2477
|
-
|
|
2625
|
+
// Call forgot password API
|
|
2626
|
+
await this.client.forgotPassword({ email });
|
|
2478
2627
|
// Show success message
|
|
2479
2628
|
this.showMessage('Reset Link Sent', `We've sent a password reset link to ${email}`);
|
|
2480
2629
|
}
|
|
@@ -2491,6 +2640,61 @@ class AuthModal {
|
|
|
2491
2640
|
submitBtn.disabled = false;
|
|
2492
2641
|
}
|
|
2493
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
|
+
}
|
|
2494
2698
|
showMessage(title, message) {
|
|
2495
2699
|
if (!this.modal)
|
|
2496
2700
|
return;
|