binoauth 0.0.11 → 0.0.13
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 +359 -165
- package/dist/core/src/admin/client.d.ts +203 -0
- package/dist/core/src/admin/client.d.ts.map +1 -0
- package/dist/core/src/admin/client.js +391 -0
- package/dist/core/src/admin/client.js.map +1 -0
- package/dist/core/src/admin/index.d.ts +6 -0
- package/dist/core/src/admin/index.d.ts.map +1 -0
- package/dist/core/src/admin/index.js +5 -0
- package/dist/core/src/admin/index.js.map +1 -0
- package/dist/core/src/admin/types.d.ts +412 -0
- package/dist/core/src/admin/types.d.ts.map +1 -0
- package/dist/core/src/admin/types.js +5 -0
- package/dist/core/src/admin/types.js.map +1 -0
- package/dist/core/src/auth/client.d.ts +330 -0
- package/dist/core/src/auth/client.d.ts.map +1 -0
- package/dist/core/src/auth/client.js +408 -0
- package/dist/core/src/auth/client.js.map +1 -0
- package/dist/core/src/auth/error.d.ts +113 -0
- package/dist/core/src/auth/error.d.ts.map +1 -0
- package/dist/core/src/auth/error.js +257 -0
- package/dist/core/src/auth/error.js.map +1 -0
- package/dist/core/src/auth/flows/base-flow.d.ts +98 -0
- package/dist/core/src/auth/flows/base-flow.d.ts.map +1 -0
- package/dist/core/src/auth/flows/base-flow.js +182 -0
- package/dist/core/src/auth/flows/base-flow.js.map +1 -0
- package/dist/core/src/auth/flows/magic-link.d.ts +175 -0
- package/dist/core/src/auth/flows/magic-link.d.ts.map +1 -0
- package/dist/core/src/auth/flows/magic-link.js +228 -0
- package/dist/core/src/auth/flows/magic-link.js.map +1 -0
- package/dist/core/src/auth/flows/mfa.d.ts +81 -0
- package/dist/core/src/auth/flows/mfa.d.ts.map +1 -0
- package/dist/core/src/auth/flows/mfa.js +103 -0
- package/dist/core/src/auth/flows/mfa.js.map +1 -0
- package/dist/core/src/auth/flows/otp.d.ts +172 -0
- package/dist/core/src/auth/flows/otp.d.ts.map +1 -0
- package/dist/core/src/auth/flows/otp.js +222 -0
- package/dist/core/src/auth/flows/otp.js.map +1 -0
- package/dist/core/src/auth/flows/password.d.ts +242 -0
- package/dist/core/src/auth/flows/password.d.ts.map +1 -0
- package/dist/core/src/auth/flows/password.js +344 -0
- package/dist/core/src/auth/flows/password.js.map +1 -0
- package/dist/core/src/auth/flows/social.d.ts +209 -0
- package/dist/core/src/auth/flows/social.d.ts.map +1 -0
- package/dist/core/src/auth/flows/social.js +284 -0
- package/dist/core/src/auth/flows/social.js.map +1 -0
- package/dist/core/src/auth/index.d.ts +19 -0
- package/dist/core/src/auth/index.d.ts.map +1 -0
- package/dist/core/src/auth/index.js +32 -0
- package/dist/core/src/auth/index.js.map +1 -0
- package/dist/core/src/auth/types.d.ts +151 -0
- package/dist/core/src/auth/types.d.ts.map +1 -0
- package/dist/core/src/auth/types.js +7 -0
- package/dist/core/src/auth/types.js.map +1 -0
- package/dist/core/src/index.d.ts +53 -49
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/index.js +61 -343
- package/dist/core/src/index.js.map +1 -1
- package/dist/core/src/oauth/client.d.ts +322 -0
- package/dist/core/src/oauth/client.d.ts.map +1 -0
- package/dist/core/src/oauth/client.js +491 -0
- package/dist/core/src/oauth/client.js.map +1 -0
- package/dist/core/src/oauth/error.d.ts +18 -0
- package/dist/core/src/oauth/error.d.ts.map +1 -0
- package/dist/core/src/oauth/error.js +24 -0
- package/dist/core/src/oauth/error.js.map +1 -0
- package/dist/core/src/oauth/flows/authorization-code.d.ts +122 -0
- package/dist/core/src/oauth/flows/authorization-code.d.ts.map +1 -0
- package/dist/core/src/oauth/flows/authorization-code.js +278 -0
- package/dist/core/src/oauth/flows/authorization-code.js.map +1 -0
- package/dist/core/src/oauth/flows/base-flow.d.ts +17 -0
- package/dist/core/src/oauth/flows/base-flow.d.ts.map +1 -0
- package/dist/core/src/oauth/flows/base-flow.js +107 -0
- package/dist/core/src/oauth/flows/base-flow.js.map +1 -0
- package/dist/core/src/oauth/flows/client-credentials.d.ts +72 -0
- package/dist/core/src/oauth/flows/client-credentials.d.ts.map +1 -0
- package/dist/core/src/oauth/flows/client-credentials.js +100 -0
- package/dist/core/src/oauth/flows/client-credentials.js.map +1 -0
- package/dist/core/src/oauth/flows/device-code.d.ts +108 -0
- package/dist/core/src/oauth/flows/device-code.d.ts.map +1 -0
- package/dist/core/src/oauth/flows/device-code.js +193 -0
- package/dist/core/src/oauth/flows/device-code.js.map +1 -0
- package/dist/core/src/oauth/flows/refresh-token.d.ts +59 -0
- package/dist/core/src/oauth/flows/refresh-token.d.ts.map +1 -0
- package/dist/core/src/oauth/flows/refresh-token.js +105 -0
- package/dist/core/src/oauth/flows/refresh-token.js.map +1 -0
- package/dist/core/src/oauth/index.d.ts +12 -0
- package/dist/core/src/oauth/index.d.ts.map +1 -0
- package/dist/core/src/oauth/index.js +11 -0
- package/dist/core/src/oauth/index.js.map +1 -0
- package/dist/core/src/oauth/storage/encryption.d.ts +12 -0
- package/dist/core/src/oauth/storage/encryption.d.ts.map +1 -0
- package/dist/core/src/oauth/storage/encryption.js +76 -0
- package/dist/core/src/oauth/storage/encryption.js.map +1 -0
- package/dist/core/src/oauth/storage/index.d.ts +201 -0
- package/dist/core/src/oauth/storage/index.d.ts.map +1 -0
- package/dist/core/src/oauth/storage/index.js +322 -0
- package/dist/core/src/oauth/storage/index.js.map +1 -0
- package/dist/core/src/oauth/storage/strategies.d.ts +34 -0
- package/dist/core/src/oauth/storage/strategies.d.ts.map +1 -0
- package/dist/core/src/oauth/storage/strategies.js +100 -0
- package/dist/core/src/oauth/storage/strategies.js.map +1 -0
- package/dist/core/src/oauth/types.d.ts +261 -0
- package/dist/core/src/oauth/types.d.ts.map +1 -0
- package/dist/core/src/oauth/types.js +39 -0
- package/dist/core/src/oauth/types.js.map +1 -0
- package/dist/core/src/oauth/utils.d.ts +56 -0
- package/dist/core/src/oauth/utils.d.ts.map +1 -0
- package/dist/core/src/oauth/utils.js +140 -0
- package/dist/core/src/oauth/utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
@@ -0,0 +1,172 @@
|
|
1
|
+
/**
|
2
|
+
* OTP (One-Time Password) authentication flow
|
3
|
+
*
|
4
|
+
* Handles authentication via SMS OTP codes using actual tenant-sdk APIs.
|
5
|
+
* Note: Email OTP is handled through the email verification flow.
|
6
|
+
*/
|
7
|
+
import type { OTPRequest, AuthResult, BinoAuthConfig } from "../types";
|
8
|
+
import { BaseAuthFlow } from "./base-flow";
|
9
|
+
/**
|
10
|
+
* OTP authentication flow
|
11
|
+
*
|
12
|
+
* Provides authentication using one-time passwords sent via SMS using actual tenant-sdk APIs.
|
13
|
+
* Uses AuthenticationApi.requestOtpViaPhoneApiV1AuthPhonePost for sending OTP and
|
14
|
+
* AuthenticationApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost for verification.
|
15
|
+
*
|
16
|
+
* @example
|
17
|
+
* ```typescript
|
18
|
+
* const otpFlow = new OTPFlow(config);
|
19
|
+
*
|
20
|
+
* // Send OTP via SMS
|
21
|
+
* await otpFlow.sendPhoneOTP('+1234567890');
|
22
|
+
*
|
23
|
+
* // User enters the OTP code
|
24
|
+
* const userEnteredCode = '123456';
|
25
|
+
* const result = await otpFlow.verifyPhoneOTP(userEnteredCode);
|
26
|
+
*
|
27
|
+
* if (result.success) {
|
28
|
+
* console.log('Authentication successful:', result.user);
|
29
|
+
* }
|
30
|
+
* ```
|
31
|
+
*/
|
32
|
+
export declare class OTPFlow extends BaseAuthFlow {
|
33
|
+
constructor(config: BinoAuthConfig);
|
34
|
+
/**
|
35
|
+
* Note: Email OTP functionality is handled through the email verification flow.
|
36
|
+
* Use the PasswordFlow.resendEmailVerification() and PasswordFlow.verifyEmail() methods
|
37
|
+
* for email-based verification instead.
|
38
|
+
*
|
39
|
+
* The tenant-sdk uses email verification tokens rather than OTP codes for email verification.
|
40
|
+
*/
|
41
|
+
/**
|
42
|
+
* Sends an OTP code to the user's phone number using tenant-sdk AuthenticationApi.requestOtpViaPhoneApiV1AuthPhonePost
|
43
|
+
*
|
44
|
+
* @param phone - User's phone number (with country code, e.g., +1234567890)
|
45
|
+
* @param allowedCountries - Optional array of allowed country codes
|
46
|
+
* @returns Promise resolving when OTP is sent
|
47
|
+
*
|
48
|
+
* @example
|
49
|
+
* ```typescript
|
50
|
+
* await otpFlow.sendPhoneOTP('+1234567890');
|
51
|
+
* console.log('Verification code sent to your phone');
|
52
|
+
*
|
53
|
+
* // With allowed countries restriction
|
54
|
+
* await otpFlow.sendPhoneOTP('+1234567890', ['US', 'CA']);
|
55
|
+
* ```
|
56
|
+
*
|
57
|
+
* @throws {AuthError} When sending OTP fails
|
58
|
+
*/
|
59
|
+
sendPhoneOTP(phone: string, allowedCountries?: string[]): Promise<void>;
|
60
|
+
/**
|
61
|
+
* Sends an OTP using the provided request configuration
|
62
|
+
*
|
63
|
+
* Note: Only SMS OTP is supported. For email verification, use the PasswordFlow class.
|
64
|
+
*
|
65
|
+
* @param request - OTP request with phone and method
|
66
|
+
* @returns Promise resolving when OTP is sent
|
67
|
+
*
|
68
|
+
* @example
|
69
|
+
* ```typescript
|
70
|
+
* // SMS OTP
|
71
|
+
* await otpFlow.sendOTP({
|
72
|
+
* phone: '+1234567890',
|
73
|
+
* method: 'sms',
|
74
|
+
* allowedCountries: ['US', 'CA']
|
75
|
+
* });
|
76
|
+
* ```
|
77
|
+
*
|
78
|
+
* @throws {AuthError} When sending OTP fails
|
79
|
+
*/
|
80
|
+
sendOTP(request: OTPRequest): Promise<void>;
|
81
|
+
/**
|
82
|
+
* Verifies a phone OTP code using tenant-sdk AuthenticationApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost
|
83
|
+
*
|
84
|
+
* Note: Only SMS OTP verification is supported. For email verification, use PasswordFlow.verifyEmail().
|
85
|
+
*
|
86
|
+
* @param code - OTP code entered by the user
|
87
|
+
* @returns Promise resolving to authentication result
|
88
|
+
*
|
89
|
+
* @example
|
90
|
+
* ```typescript
|
91
|
+
* try {
|
92
|
+
* const result = await otpFlow.verifyPhoneOTP('123456');
|
93
|
+
* if (result.success) {
|
94
|
+
* console.log('Welcome,', result.user.name);
|
95
|
+
* localStorage.setItem('accessToken', result.accessToken);
|
96
|
+
* }
|
97
|
+
* } catch (error) {
|
98
|
+
* if (error.code === AuthErrorCode.INVALID_OTP) {
|
99
|
+
* console.log('Invalid verification code. Please try again.');
|
100
|
+
* } else if (error.code === AuthErrorCode.EXPIRED_OTP) {
|
101
|
+
* console.log('Verification code has expired. Please request a new one.');
|
102
|
+
* }
|
103
|
+
* }
|
104
|
+
* ```
|
105
|
+
*
|
106
|
+
* @throws {AuthError} When OTP verification fails
|
107
|
+
*/
|
108
|
+
verifyPhoneOTP(code: string): Promise<AuthResult>;
|
109
|
+
/**
|
110
|
+
* Note: Email OTP verification is not supported.
|
111
|
+
* Use PasswordFlow.verifyEmail() for email verification instead.
|
112
|
+
*/
|
113
|
+
/**
|
114
|
+
* Note: Email OTP resending is not supported.
|
115
|
+
* Use PasswordFlow.resendEmailVerification() for email verification instead.
|
116
|
+
*/
|
117
|
+
/**
|
118
|
+
* Resends an OTP to the same phone number
|
119
|
+
*
|
120
|
+
* @param phone - Phone number to resend OTP to
|
121
|
+
* @param allowedCountries - Optional array of allowed country codes
|
122
|
+
* @returns Promise resolving when OTP is resent
|
123
|
+
*
|
124
|
+
* @example
|
125
|
+
* ```typescript
|
126
|
+
* // User clicks "Resend code" button
|
127
|
+
* await otpFlow.resendPhoneOTP('+1234567890');
|
128
|
+
* console.log('New verification code sent to your phone');
|
129
|
+
* ```
|
130
|
+
*/
|
131
|
+
resendPhoneOTP(phone: string, allowedCountries?: string[]): Promise<void>;
|
132
|
+
/**
|
133
|
+
* Validates an OTP code format without verifying it
|
134
|
+
*
|
135
|
+
* @param code - OTP code to validate
|
136
|
+
* @returns True if format is valid, false otherwise
|
137
|
+
*
|
138
|
+
* @example
|
139
|
+
* ```typescript
|
140
|
+
* const userInput = '123456';
|
141
|
+
* if (otpFlow.validateOTPFormat(userInput)) {
|
142
|
+
* // Proceed with verification
|
143
|
+
* const result = await otpFlow.verifyPhoneOTP(userInput);
|
144
|
+
* } else {
|
145
|
+
* console.log('Please enter a valid 6-digit code');
|
146
|
+
* }
|
147
|
+
* ```
|
148
|
+
*/
|
149
|
+
validateOTPFormat(code: string): boolean;
|
150
|
+
/**
|
151
|
+
* Gets remaining time before OTP expires (if available from server)
|
152
|
+
*
|
153
|
+
* This is a placeholder method that would typically query the server
|
154
|
+
* for OTP expiration information. The tenant-sdk doesn't currently
|
155
|
+
* provide an endpoint for checking OTP status.
|
156
|
+
*
|
157
|
+
* @param phone - Phone number that received the OTP
|
158
|
+
* @returns Promise resolving to remaining seconds, or null if unknown
|
159
|
+
*
|
160
|
+
* @example
|
161
|
+
* ```typescript
|
162
|
+
* const remainingTime = await otpFlow.getOTPRemainingTime('+1234567890');
|
163
|
+
* if (remainingTime && remainingTime > 0) {
|
164
|
+
* console.log(`Code expires in ${remainingTime} seconds`);
|
165
|
+
* } else {
|
166
|
+
* console.log('Code may have expired');
|
167
|
+
* }
|
168
|
+
* ```
|
169
|
+
*/
|
170
|
+
getOTPRemainingTime(phone: string): Promise<number | null>;
|
171
|
+
}
|
172
|
+
//# sourceMappingURL=otp.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"otp.d.ts","sourceRoot":"","sources":["../../../../../src/auth/flows/otp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAmB,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAExF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,OAAQ,SAAQ,YAAY;gBAE3B,MAAM,EAAE,cAAc;IAIlC;;;;;;OAMG;IAEH;;;;;;;;;;;;;;;;;OAiBG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7E;;;;;;;;;;;;;;;;;;;OAmBG;IACG,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAuBvD;;;OAGG;IAEH;;;OAGG;IAEH;;;;;;;;;;;;;OAaG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQxC;;;;;;;;;;;;;;;;;;;OAmBG;IACG,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAKjE"}
|
@@ -0,0 +1,222 @@
|
|
1
|
+
/**
|
2
|
+
* OTP (One-Time Password) authentication flow
|
3
|
+
*
|
4
|
+
* Handles authentication via SMS OTP codes using actual tenant-sdk APIs.
|
5
|
+
* Note: Email OTP is handled through the email verification flow.
|
6
|
+
*/
|
7
|
+
import { AuthError, AuthErrorCode } from "../error";
|
8
|
+
import { BaseAuthFlow } from "./base-flow";
|
9
|
+
/**
|
10
|
+
* OTP authentication flow
|
11
|
+
*
|
12
|
+
* Provides authentication using one-time passwords sent via SMS using actual tenant-sdk APIs.
|
13
|
+
* Uses AuthenticationApi.requestOtpViaPhoneApiV1AuthPhonePost for sending OTP and
|
14
|
+
* AuthenticationApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost for verification.
|
15
|
+
*
|
16
|
+
* @example
|
17
|
+
* ```typescript
|
18
|
+
* const otpFlow = new OTPFlow(config);
|
19
|
+
*
|
20
|
+
* // Send OTP via SMS
|
21
|
+
* await otpFlow.sendPhoneOTP('+1234567890');
|
22
|
+
*
|
23
|
+
* // User enters the OTP code
|
24
|
+
* const userEnteredCode = '123456';
|
25
|
+
* const result = await otpFlow.verifyPhoneOTP(userEnteredCode);
|
26
|
+
*
|
27
|
+
* if (result.success) {
|
28
|
+
* console.log('Authentication successful:', result.user);
|
29
|
+
* }
|
30
|
+
* ```
|
31
|
+
*/
|
32
|
+
export class OTPFlow extends BaseAuthFlow {
|
33
|
+
constructor(config) {
|
34
|
+
super(config);
|
35
|
+
}
|
36
|
+
/**
|
37
|
+
* Note: Email OTP functionality is handled through the email verification flow.
|
38
|
+
* Use the PasswordFlow.resendEmailVerification() and PasswordFlow.verifyEmail() methods
|
39
|
+
* for email-based verification instead.
|
40
|
+
*
|
41
|
+
* The tenant-sdk uses email verification tokens rather than OTP codes for email verification.
|
42
|
+
*/
|
43
|
+
/**
|
44
|
+
* Sends an OTP code to the user's phone number using tenant-sdk AuthenticationApi.requestOtpViaPhoneApiV1AuthPhonePost
|
45
|
+
*
|
46
|
+
* @param phone - User's phone number (with country code, e.g., +1234567890)
|
47
|
+
* @param allowedCountries - Optional array of allowed country codes
|
48
|
+
* @returns Promise resolving when OTP is sent
|
49
|
+
*
|
50
|
+
* @example
|
51
|
+
* ```typescript
|
52
|
+
* await otpFlow.sendPhoneOTP('+1234567890');
|
53
|
+
* console.log('Verification code sent to your phone');
|
54
|
+
*
|
55
|
+
* // With allowed countries restriction
|
56
|
+
* await otpFlow.sendPhoneOTP('+1234567890', ['US', 'CA']);
|
57
|
+
* ```
|
58
|
+
*
|
59
|
+
* @throws {AuthError} When sending OTP fails
|
60
|
+
*/
|
61
|
+
async sendPhoneOTP(phone, allowedCountries) {
|
62
|
+
this.validatePhone(phone);
|
63
|
+
await this.safeApiCall(async () => {
|
64
|
+
// Use the actual tenant-sdk AuthenticationApi.requestOtpViaPhoneApiV1AuthPhonePost method
|
65
|
+
await this.authApi.requestOtpViaPhoneApiV1AuthPhonePost({
|
66
|
+
phoneOTPRequest: {
|
67
|
+
phone,
|
68
|
+
allowedCountries: allowedCountries || null,
|
69
|
+
},
|
70
|
+
});
|
71
|
+
}, AuthErrorCode.ACCOUNT_NOT_FOUND);
|
72
|
+
}
|
73
|
+
/**
|
74
|
+
* Sends an OTP using the provided request configuration
|
75
|
+
*
|
76
|
+
* Note: Only SMS OTP is supported. For email verification, use the PasswordFlow class.
|
77
|
+
*
|
78
|
+
* @param request - OTP request with phone and method
|
79
|
+
* @returns Promise resolving when OTP is sent
|
80
|
+
*
|
81
|
+
* @example
|
82
|
+
* ```typescript
|
83
|
+
* // SMS OTP
|
84
|
+
* await otpFlow.sendOTP({
|
85
|
+
* phone: '+1234567890',
|
86
|
+
* method: 'sms',
|
87
|
+
* allowedCountries: ['US', 'CA']
|
88
|
+
* });
|
89
|
+
* ```
|
90
|
+
*
|
91
|
+
* @throws {AuthError} When sending OTP fails
|
92
|
+
*/
|
93
|
+
async sendOTP(request) {
|
94
|
+
if (request.method === 'sms') {
|
95
|
+
if (!request.phone) {
|
96
|
+
throw new AuthError(AuthErrorCode.MISSING_REQUIRED_FIELD, 'Phone number is required for SMS OTP');
|
97
|
+
}
|
98
|
+
await this.sendPhoneOTP(request.phone, request.allowedCountries);
|
99
|
+
}
|
100
|
+
else {
|
101
|
+
throw new AuthError(AuthErrorCode.INVALID_CONFIG, 'Invalid OTP method. Only "sms" is supported.');
|
102
|
+
}
|
103
|
+
}
|
104
|
+
/**
|
105
|
+
* Verifies a phone OTP code using tenant-sdk AuthenticationApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost
|
106
|
+
*
|
107
|
+
* Note: Only SMS OTP verification is supported. For email verification, use PasswordFlow.verifyEmail().
|
108
|
+
*
|
109
|
+
* @param code - OTP code entered by the user
|
110
|
+
* @returns Promise resolving to authentication result
|
111
|
+
*
|
112
|
+
* @example
|
113
|
+
* ```typescript
|
114
|
+
* try {
|
115
|
+
* const result = await otpFlow.verifyPhoneOTP('123456');
|
116
|
+
* if (result.success) {
|
117
|
+
* console.log('Welcome,', result.user.name);
|
118
|
+
* localStorage.setItem('accessToken', result.accessToken);
|
119
|
+
* }
|
120
|
+
* } catch (error) {
|
121
|
+
* if (error.code === AuthErrorCode.INVALID_OTP) {
|
122
|
+
* console.log('Invalid verification code. Please try again.');
|
123
|
+
* } else if (error.code === AuthErrorCode.EXPIRED_OTP) {
|
124
|
+
* console.log('Verification code has expired. Please request a new one.');
|
125
|
+
* }
|
126
|
+
* }
|
127
|
+
* ```
|
128
|
+
*
|
129
|
+
* @throws {AuthError} When OTP verification fails
|
130
|
+
*/
|
131
|
+
async verifyPhoneOTP(code) {
|
132
|
+
if (!code || code.length < 4) {
|
133
|
+
throw new AuthError(AuthErrorCode.INVALID_OTP, 'Valid OTP code is required');
|
134
|
+
}
|
135
|
+
// Normalize the code (remove spaces, ensure it's a string)
|
136
|
+
const normalizedCode = code.replace(/\s+/g, '').toString();
|
137
|
+
return this.safeApiCall(async () => {
|
138
|
+
// Use the actual tenant-sdk AuthenticationApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost method
|
139
|
+
const response = await this.authApi.verifyPhoneOtpApiV1AuthPhoneVerifyPost({
|
140
|
+
phoneOTPVerificationRequest: {
|
141
|
+
otp: normalizedCode,
|
142
|
+
},
|
143
|
+
});
|
144
|
+
return this.processAuthResponse(response);
|
145
|
+
}, AuthErrorCode.INVALID_OTP);
|
146
|
+
}
|
147
|
+
/**
|
148
|
+
* Note: Email OTP verification is not supported.
|
149
|
+
* Use PasswordFlow.verifyEmail() for email verification instead.
|
150
|
+
*/
|
151
|
+
/**
|
152
|
+
* Note: Email OTP resending is not supported.
|
153
|
+
* Use PasswordFlow.resendEmailVerification() for email verification instead.
|
154
|
+
*/
|
155
|
+
/**
|
156
|
+
* Resends an OTP to the same phone number
|
157
|
+
*
|
158
|
+
* @param phone - Phone number to resend OTP to
|
159
|
+
* @param allowedCountries - Optional array of allowed country codes
|
160
|
+
* @returns Promise resolving when OTP is resent
|
161
|
+
*
|
162
|
+
* @example
|
163
|
+
* ```typescript
|
164
|
+
* // User clicks "Resend code" button
|
165
|
+
* await otpFlow.resendPhoneOTP('+1234567890');
|
166
|
+
* console.log('New verification code sent to your phone');
|
167
|
+
* ```
|
168
|
+
*/
|
169
|
+
async resendPhoneOTP(phone, allowedCountries) {
|
170
|
+
return this.sendPhoneOTP(phone, allowedCountries);
|
171
|
+
}
|
172
|
+
/**
|
173
|
+
* Validates an OTP code format without verifying it
|
174
|
+
*
|
175
|
+
* @param code - OTP code to validate
|
176
|
+
* @returns True if format is valid, false otherwise
|
177
|
+
*
|
178
|
+
* @example
|
179
|
+
* ```typescript
|
180
|
+
* const userInput = '123456';
|
181
|
+
* if (otpFlow.validateOTPFormat(userInput)) {
|
182
|
+
* // Proceed with verification
|
183
|
+
* const result = await otpFlow.verifyPhoneOTP(userInput);
|
184
|
+
* } else {
|
185
|
+
* console.log('Please enter a valid 6-digit code');
|
186
|
+
* }
|
187
|
+
* ```
|
188
|
+
*/
|
189
|
+
validateOTPFormat(code) {
|
190
|
+
if (!code)
|
191
|
+
return false;
|
192
|
+
// Remove spaces and check if it's all digits and appropriate length
|
193
|
+
const normalizedCode = code.replace(/\s+/g, '');
|
194
|
+
return /^\d{4,8}$/.test(normalizedCode);
|
195
|
+
}
|
196
|
+
/**
|
197
|
+
* Gets remaining time before OTP expires (if available from server)
|
198
|
+
*
|
199
|
+
* This is a placeholder method that would typically query the server
|
200
|
+
* for OTP expiration information. The tenant-sdk doesn't currently
|
201
|
+
* provide an endpoint for checking OTP status.
|
202
|
+
*
|
203
|
+
* @param phone - Phone number that received the OTP
|
204
|
+
* @returns Promise resolving to remaining seconds, or null if unknown
|
205
|
+
*
|
206
|
+
* @example
|
207
|
+
* ```typescript
|
208
|
+
* const remainingTime = await otpFlow.getOTPRemainingTime('+1234567890');
|
209
|
+
* if (remainingTime && remainingTime > 0) {
|
210
|
+
* console.log(`Code expires in ${remainingTime} seconds`);
|
211
|
+
* } else {
|
212
|
+
* console.log('Code may have expired');
|
213
|
+
* }
|
214
|
+
* ```
|
215
|
+
*/
|
216
|
+
async getOTPRemainingTime(phone) {
|
217
|
+
// The tenant-sdk doesn't provide an endpoint for checking OTP status
|
218
|
+
// This would need to be implemented on the server side
|
219
|
+
return null;
|
220
|
+
}
|
221
|
+
}
|
222
|
+
//# sourceMappingURL=otp.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"otp.js","sourceRoot":"","sources":["../../../../../src/auth/flows/otp.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,242 @@
|
|
1
|
+
/**
|
2
|
+
* Password-based authentication flow
|
3
|
+
*
|
4
|
+
* Handles email/password login, registration using the actual tenant-sdk APIs.
|
5
|
+
*/
|
6
|
+
import type { LoginRequest, SignupRequest, AuthResult, User, PasswordResetRequest, PasswordResetData, BinoAuthConfig } from "../types";
|
7
|
+
import { BaseAuthFlow } from "./base-flow";
|
8
|
+
/**
|
9
|
+
* Password authentication flow
|
10
|
+
*
|
11
|
+
* Provides email/password authentication using the actual tenant-sdk AuthenticationApi.
|
12
|
+
* All methods use the real API endpoints available in the tenant-sdk.
|
13
|
+
*
|
14
|
+
* @example
|
15
|
+
* ```typescript
|
16
|
+
* const passwordFlow = new PasswordFlow(config);
|
17
|
+
*
|
18
|
+
* // Login with email and password
|
19
|
+
* try {
|
20
|
+
* const result = await passwordFlow.login('user@example.com', 'password123');
|
21
|
+
* if (result.success) {
|
22
|
+
* console.log('Login successful:', result.user);
|
23
|
+
* console.log('Access token:', result.accessToken);
|
24
|
+
* }
|
25
|
+
* } catch (error) {
|
26
|
+
* if (error instanceof AuthError) {
|
27
|
+
* console.log('Login failed:', error.message);
|
28
|
+
* }
|
29
|
+
* }
|
30
|
+
*
|
31
|
+
* // Register new user
|
32
|
+
* const userData = {
|
33
|
+
* email: 'newuser@example.com',
|
34
|
+
* password: 'securepassword',
|
35
|
+
* name: 'John Doe',
|
36
|
+
* acceptTerms: true
|
37
|
+
* };
|
38
|
+
*
|
39
|
+
* const registerResult = await passwordFlow.register(userData);
|
40
|
+
* ```
|
41
|
+
*/
|
42
|
+
export declare class PasswordFlow extends BaseAuthFlow {
|
43
|
+
constructor(config: BinoAuthConfig);
|
44
|
+
/**
|
45
|
+
* Authenticates a user with email and password using tenant-sdk AuthenticationApi.loginApiV1AuthLoginPost
|
46
|
+
*
|
47
|
+
* @param email - User's email address
|
48
|
+
* @param password - User's password
|
49
|
+
* @param rememberMe - Whether to remember the user (longer token expiry)
|
50
|
+
* @returns Promise resolving to authentication result
|
51
|
+
*
|
52
|
+
* @example
|
53
|
+
* ```typescript
|
54
|
+
* try {
|
55
|
+
* const result = await passwordFlow.login('user@example.com', 'password123');
|
56
|
+
* if (result.success) {
|
57
|
+
* localStorage.setItem('accessToken', result.accessToken);
|
58
|
+
* console.log('Welcome,', result.user.name);
|
59
|
+
* }
|
60
|
+
* } catch (error) {
|
61
|
+
* if (error.code === AuthErrorCode.INVALID_CREDENTIALS) {
|
62
|
+
* console.log('Invalid email or password');
|
63
|
+
* } else if (error.code === AuthErrorCode.MFA_REQUIRED) {
|
64
|
+
* console.log('MFA required:', error.details);
|
65
|
+
* }
|
66
|
+
* }
|
67
|
+
* ```
|
68
|
+
*
|
69
|
+
* @throws {AuthError} When credentials are invalid or authentication fails
|
70
|
+
*/
|
71
|
+
login(email: string, password: string, rememberMe?: boolean): Promise<AuthResult>;
|
72
|
+
/**
|
73
|
+
* Authenticates a user with credentials object using tenant-sdk LoginRequest
|
74
|
+
*
|
75
|
+
* @param credentials - Login credentials from tenant-sdk
|
76
|
+
* @returns Promise resolving to authentication result
|
77
|
+
*
|
78
|
+
* @example
|
79
|
+
* ```typescript
|
80
|
+
* const credentials: LoginRequest = {
|
81
|
+
* email: 'user@example.com',
|
82
|
+
* password: 'password123',
|
83
|
+
* rememberMe: true
|
84
|
+
* };
|
85
|
+
*
|
86
|
+
* const result = await passwordFlow.loginWithCredentials(credentials);
|
87
|
+
* ```
|
88
|
+
*/
|
89
|
+
loginWithCredentials(credentials: LoginRequest): Promise<AuthResult>;
|
90
|
+
/**
|
91
|
+
* Registers a new user account using tenant-sdk AuthenticationApi.signupApiV1AuthSignupPost
|
92
|
+
*
|
93
|
+
* @param userData - User registration data using tenant-sdk SignupRequest
|
94
|
+
* @returns Promise resolving to authentication result
|
95
|
+
*
|
96
|
+
* @example
|
97
|
+
* ```typescript
|
98
|
+
* const userData: SignupRequest = {
|
99
|
+
* email: 'newuser@example.com',
|
100
|
+
* password: 'securepassword123',
|
101
|
+
* confirmPassword: 'securepassword123',
|
102
|
+
* name: 'John Doe',
|
103
|
+
* returnTo: 'https://myapp.com/welcome'
|
104
|
+
* };
|
105
|
+
*
|
106
|
+
* try {
|
107
|
+
* const result = await passwordFlow.register(userData);
|
108
|
+
* console.log('Registration successful:', result);
|
109
|
+
* } catch (error) {
|
110
|
+
* if (error.code === AuthErrorCode.EMAIL_ALREADY_EXISTS) {
|
111
|
+
* console.log('Account with this email already exists');
|
112
|
+
* } else if (error.code === AuthErrorCode.WEAK_PASSWORD) {
|
113
|
+
* console.log('Password is too weak');
|
114
|
+
* }
|
115
|
+
* }
|
116
|
+
* ```
|
117
|
+
*
|
118
|
+
* @throws {AuthError} When registration fails
|
119
|
+
*/
|
120
|
+
register(userData: SignupRequest): Promise<AuthResult>;
|
121
|
+
/**
|
122
|
+
* Requests email verification resend using tenant-sdk AuthenticationApi.resendVerificationApiV1AuthResendVerificationPost
|
123
|
+
*
|
124
|
+
* @param email - User's email address
|
125
|
+
* @returns Promise resolving when verification email is sent
|
126
|
+
*
|
127
|
+
* @example
|
128
|
+
* ```typescript
|
129
|
+
* await passwordFlow.resendEmailVerification('user@example.com');
|
130
|
+
* console.log('Verification email sent');
|
131
|
+
* ```
|
132
|
+
*
|
133
|
+
* @throws {AuthError} When resend request fails
|
134
|
+
*/
|
135
|
+
resendEmailVerification(email: string): Promise<void>;
|
136
|
+
/**
|
137
|
+
* Verifies email address using tenant-sdk AuthenticationApi.verifyEmailApiV1AuthVerifyEmailPost
|
138
|
+
*
|
139
|
+
* @param token - Email verification token
|
140
|
+
* @returns Promise resolving to authentication result
|
141
|
+
*
|
142
|
+
* @example
|
143
|
+
* ```typescript
|
144
|
+
* // Token comes from email link
|
145
|
+
* const result = await passwordFlow.verifyEmail(token);
|
146
|
+
* if (result.success) {
|
147
|
+
* console.log('Email verified successfully');
|
148
|
+
* }
|
149
|
+
* ```
|
150
|
+
*
|
151
|
+
* @throws {AuthError} When email verification fails
|
152
|
+
*/
|
153
|
+
verifyEmail(token: string): Promise<AuthResult>;
|
154
|
+
/**
|
155
|
+
* Logs out the current user using tenant-sdk AuthenticationApi.logoutApiV1AuthLogoutPost
|
156
|
+
*
|
157
|
+
* @returns Promise resolving when logout is complete
|
158
|
+
*
|
159
|
+
* @example
|
160
|
+
* ```typescript
|
161
|
+
* await passwordFlow.logout();
|
162
|
+
* console.log('Logged out successfully');
|
163
|
+
* ```
|
164
|
+
*
|
165
|
+
* @throws {AuthError} When logout fails
|
166
|
+
*/
|
167
|
+
logout(): Promise<void>;
|
168
|
+
/**
|
169
|
+
* Gets current user information using tenant-sdk UserProfileApi.getCurrentUserApiV1AuthUserinfoGet
|
170
|
+
*
|
171
|
+
* @returns Promise resolving to user information
|
172
|
+
*
|
173
|
+
* @example
|
174
|
+
* ```typescript
|
175
|
+
* const user = await passwordFlow.getCurrentUser();
|
176
|
+
* console.log('Current user:', user.name, user.email);
|
177
|
+
* ```
|
178
|
+
*
|
179
|
+
* @throws {AuthError} When user info fetch fails
|
180
|
+
*/
|
181
|
+
getCurrentUser(): Promise<User>;
|
182
|
+
/**
|
183
|
+
* Note: Password reset functionality depends on magic link flow
|
184
|
+
* The tenant-sdk doesn't have dedicated password reset endpoints,
|
185
|
+
* instead it uses magic links for password reset flows.
|
186
|
+
*
|
187
|
+
* For password reset, use the MagicLinkFlow class which uses:
|
188
|
+
* - AuthenticationApi.requestMagicLinkApiV1AuthMlPost for reset request
|
189
|
+
* - AuthenticationApi.verifyMagicLinkApiV1AuthMlVerifyPost for reset verification
|
190
|
+
*/
|
191
|
+
/**
|
192
|
+
* Initiates password reset process using magic link
|
193
|
+
*
|
194
|
+
* @param request - Password reset request
|
195
|
+
* @returns Promise resolving when reset email is sent
|
196
|
+
*
|
197
|
+
* @example
|
198
|
+
* ```typescript
|
199
|
+
* await passwordFlow.requestPasswordReset({
|
200
|
+
* email: 'user@example.com',
|
201
|
+
* returnTo: 'https://myapp.com/reset-complete'
|
202
|
+
* });
|
203
|
+
*
|
204
|
+
* console.log('Password reset email sent');
|
205
|
+
* ```
|
206
|
+
*
|
207
|
+
* @throws {AuthError} When reset request fails
|
208
|
+
*/
|
209
|
+
requestPasswordReset(request: PasswordResetRequest): Promise<void>;
|
210
|
+
/**
|
211
|
+
* Completes password reset with magic link token
|
212
|
+
*
|
213
|
+
* @param resetData - Password reset completion data
|
214
|
+
* @returns Promise resolving to authentication result
|
215
|
+
*
|
216
|
+
* @example
|
217
|
+
* ```typescript
|
218
|
+
* // Token comes from magic link
|
219
|
+
* const resetData = {
|
220
|
+
* token: 'magic_link_token_from_email',
|
221
|
+
* newPassword: 'newsecurepassword123',
|
222
|
+
* confirmPassword: 'newsecurepassword123'
|
223
|
+
* };
|
224
|
+
*
|
225
|
+
* try {
|
226
|
+
* const result = await passwordFlow.resetPassword(resetData);
|
227
|
+
* if (result.success) {
|
228
|
+
* console.log('Password reset successful');
|
229
|
+
* // User is automatically logged in after password reset
|
230
|
+
* }
|
231
|
+
* } catch (error) {
|
232
|
+
* if (error.code === AuthErrorCode.INVALID_TOKEN) {
|
233
|
+
* console.log('Reset token is invalid or expired');
|
234
|
+
* }
|
235
|
+
* }
|
236
|
+
* ```
|
237
|
+
*
|
238
|
+
* @throws {AuthError} When password reset fails
|
239
|
+
*/
|
240
|
+
resetPassword(resetData: PasswordResetData): Promise<AuthResult>;
|
241
|
+
}
|
242
|
+
//# sourceMappingURL=password.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"password.d.ts","sourceRoot":"","sources":["../../../../../src/auth/flows/password.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,IAAI,EACJ,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACf,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,YAAa,SAAQ,YAAY;gBAEhC,MAAM,EAAE,cAAc;IAIlC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAE,OAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAwB9F;;;;;;;;;;;;;;;;OAgBG;IACG,oBAAoB,CAAC,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAW1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;IAsB5D;;;;;;;;;;;;;OAaG;IACG,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3D;;;;;;;;;;;;;;;;OAgBG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBrD;;;;;;;;;;;;OAYG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B;;;;;;;;;;;;OAYG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IASrC;;;;;;;;OAQG;IAEH;;;;;;;;;;;;;;;;;OAiBG;IACG,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;CA6BvE"}
|