binoauth 0.0.10 → 0.0.12
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,278 @@
|
|
1
|
+
import { TokenStorage } from "../storage";
|
2
|
+
import { generatePKCEPair, generateState, validateState } from "../utils";
|
3
|
+
import { AuthError, ErrorCode } from "../error";
|
4
|
+
import { BaseFlow } from "./base-flow";
|
5
|
+
/**
|
6
|
+
* OAuth 2.0 Authorization Code Flow with PKCE support
|
7
|
+
*
|
8
|
+
* This flow is designed for browser-based applications (SPAs) and mobile apps
|
9
|
+
* where the client cannot securely store a client secret. It uses PKCE
|
10
|
+
* (Proof Key for Code Exchange) for enhanced security.
|
11
|
+
*
|
12
|
+
* @example
|
13
|
+
* ```typescript
|
14
|
+
* const flow = new AuthorizationCodeFlow(config, storage, oauthApi);
|
15
|
+
*
|
16
|
+
* // Step 1: Get authorization URL
|
17
|
+
* const loginUrl = await flow.getLoginUrl();
|
18
|
+
* window.location.href = loginUrl; // Redirect user to BinoAuth
|
19
|
+
*
|
20
|
+
* // Step 2: Handle callback (after user returns from BinoAuth)
|
21
|
+
* const urlParams = new URLSearchParams(window.location.search);
|
22
|
+
* const code = urlParams.get('code');
|
23
|
+
* const state = urlParams.get('state');
|
24
|
+
*
|
25
|
+
* if (code && state) {
|
26
|
+
* await flow.handleCallback(code, state);
|
27
|
+
* // User is now authenticated, tokens are stored
|
28
|
+
* }
|
29
|
+
* ```
|
30
|
+
*/
|
31
|
+
export class AuthorizationCodeFlow extends BaseFlow {
|
32
|
+
loginUrlGenerationInProgress = false;
|
33
|
+
constructor(config, storage, oauthApi) {
|
34
|
+
super(config, storage, oauthApi);
|
35
|
+
if (!config.authorizeEndpoint) {
|
36
|
+
throw new AuthError("Missing authorizeEndpoint in config", ErrorCode.InvalidConfig);
|
37
|
+
}
|
38
|
+
if (!config.tokenEndpoint) {
|
39
|
+
throw new AuthError("Missing tokenEndpoint in config", ErrorCode.InvalidConfig);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
/**
|
43
|
+
* Generates the authorization URL for initiating the OAuth flow
|
44
|
+
*
|
45
|
+
* This method creates a secure authorization URL with PKCE parameters
|
46
|
+
* and stores the necessary state and verifier tokens for later validation.
|
47
|
+
*
|
48
|
+
* @returns Promise resolving to the authorization URL
|
49
|
+
*
|
50
|
+
* @example
|
51
|
+
* ```typescript
|
52
|
+
* const loginUrl = await flow.getLoginUrl();
|
53
|
+
* // Returns: "https://auth.binoauth.com/api/v1/oauth/authorize?response_type=code&client_id=..."
|
54
|
+
*
|
55
|
+
* // Redirect user to the authorization server
|
56
|
+
* window.location.href = loginUrl;
|
57
|
+
* ```
|
58
|
+
*
|
59
|
+
* @throws {AuthError} When authorization endpoint is missing from config
|
60
|
+
*/
|
61
|
+
async getLoginUrl() {
|
62
|
+
if (this.loginUrlGenerationInProgress) {
|
63
|
+
console.warn("OAuth login URL generation already in progress - potential race condition");
|
64
|
+
}
|
65
|
+
this.loginUrlGenerationInProgress = true;
|
66
|
+
try {
|
67
|
+
const { verifier, challenge } = await generatePKCEPair();
|
68
|
+
const state = generateState();
|
69
|
+
console.debug("OAuth login URL generation", {
|
70
|
+
generatedState: state,
|
71
|
+
stateLength: state.length,
|
72
|
+
generatedVerifier: verifier.substring(0, 10) + "...",
|
73
|
+
timestamp: new Date().toISOString()
|
74
|
+
});
|
75
|
+
await this.storage.setTokens({
|
76
|
+
state: {
|
77
|
+
value: state,
|
78
|
+
expiresAt: Date.now() + 1200000, // 20 minutes
|
79
|
+
},
|
80
|
+
verifier: {
|
81
|
+
value: verifier,
|
82
|
+
expiresAt: Date.now() + 1200000, // 20 minutes
|
83
|
+
},
|
84
|
+
});
|
85
|
+
const storedStateToken = await this.storage.getStateToken();
|
86
|
+
console.debug("OAuth state storage verification", {
|
87
|
+
generatedState: state,
|
88
|
+
storedState: storedStateToken?.value,
|
89
|
+
storageWorked: storedStateToken?.value === state,
|
90
|
+
storageExpiry: storedStateToken?.expiresAt
|
91
|
+
});
|
92
|
+
const params = new URLSearchParams({
|
93
|
+
response_type: "code",
|
94
|
+
client_id: this.config.clientId,
|
95
|
+
scope: this.config.scope,
|
96
|
+
state,
|
97
|
+
code_challenge: challenge,
|
98
|
+
code_challenge_method: "S256",
|
99
|
+
redirect_uri: this.config.redirectUri,
|
100
|
+
});
|
101
|
+
const finalUrl = `${this.config.authorizeEndpoint}?${params.toString()}`;
|
102
|
+
console.debug("OAuth login URL complete", {
|
103
|
+
finalUrl,
|
104
|
+
stateInUrl: params.get('state'),
|
105
|
+
stateMatches: params.get('state') === state,
|
106
|
+
urlLength: finalUrl.length
|
107
|
+
});
|
108
|
+
return finalUrl;
|
109
|
+
}
|
110
|
+
finally {
|
111
|
+
this.loginUrlGenerationInProgress = false;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
/**
|
115
|
+
* Handles the OAuth callback and exchanges the authorization code for tokens
|
116
|
+
*
|
117
|
+
* This method validates the state parameter for CSRF protection, then exchanges
|
118
|
+
* the authorization code for access and refresh tokens using PKCE verification.
|
119
|
+
*
|
120
|
+
* @param code - The authorization code returned from the authorization server
|
121
|
+
* @param state - The state parameter returned from the authorization server
|
122
|
+
*
|
123
|
+
* @example
|
124
|
+
* ```typescript
|
125
|
+
* // Extract parameters from callback URL
|
126
|
+
* const urlParams = new URLSearchParams(window.location.search);
|
127
|
+
* const code = urlParams.get('code')!;
|
128
|
+
* const state = urlParams.get('state')!;
|
129
|
+
*
|
130
|
+
* // Exchange code for tokens
|
131
|
+
* await flow.handleCallback(code, state);
|
132
|
+
*
|
133
|
+
* // Tokens are now stored and user is authenticated
|
134
|
+
* const accessToken = await storage.getAccessToken();
|
135
|
+
* ```
|
136
|
+
*
|
137
|
+
* @throws {AuthError} When state validation fails (CSRF protection)
|
138
|
+
* @throws {AuthError} When state or verifier tokens are missing/expired
|
139
|
+
* @throws {AuthError} When token exchange fails
|
140
|
+
*/
|
141
|
+
async handleCallback(code, state) {
|
142
|
+
this.checkRateLimit("callback", 10, 5 * 60 * 1000);
|
143
|
+
this.cleanupRateLimiter();
|
144
|
+
console.debug("OAuth callback initiated", {
|
145
|
+
codeLength: code?.length || 0,
|
146
|
+
stateLength: state?.length || 0,
|
147
|
+
timestamp: new Date().toISOString()
|
148
|
+
});
|
149
|
+
const storedStateToken = await this.storage.getStateToken();
|
150
|
+
const verifierToken = await this.storage.getVerifierToken();
|
151
|
+
console.debug("OAuth callback storage state", {
|
152
|
+
hasStoredState: !!storedStateToken,
|
153
|
+
hasVerifier: !!verifierToken,
|
154
|
+
storedStateExpired: storedStateToken ? this.storage.isTokenExpired(storedStateToken) : null,
|
155
|
+
receivedStateLength: state?.length || 0,
|
156
|
+
storedStateLength: storedStateToken?.value?.length || 0
|
157
|
+
});
|
158
|
+
if (!storedStateToken || !verifierToken) {
|
159
|
+
console.warn("OAuth callback failed: missing stored state or verifier", {
|
160
|
+
hasStoredState: !!storedStateToken,
|
161
|
+
hasVerifier: !!verifierToken
|
162
|
+
});
|
163
|
+
this.storage.clearTokens();
|
164
|
+
this.storage.clearStateAndVerifier();
|
165
|
+
throw new AuthError("Authentication session not found - please restart login process", ErrorCode.MissingVerifier);
|
166
|
+
}
|
167
|
+
console.debug("State validation comparison:", {
|
168
|
+
receivedState: state,
|
169
|
+
storedState: storedStateToken.value,
|
170
|
+
lengthsMatch: state.length === storedStateToken.value.length,
|
171
|
+
directEquals: state === storedStateToken.value
|
172
|
+
});
|
173
|
+
if (!validateState(state, storedStateToken.value)) {
|
174
|
+
console.error("State validation failed - detailed comparison:", {
|
175
|
+
receivedState: state,
|
176
|
+
storedState: storedStateToken.value,
|
177
|
+
receivedLength: state.length,
|
178
|
+
storedLength: storedStateToken.value.length,
|
179
|
+
directEquals: state === storedStateToken.value,
|
180
|
+
charDiffs: Array.from({ length: Math.min(state.length, storedStateToken.value.length, 10) }, (_, i) => state.charAt(i) !== storedStateToken.value.charAt(i) ?
|
181
|
+
`pos${i}: '${state.charAt(i)}' vs '${storedStateToken.value.charAt(i)}'` : null).filter(Boolean)
|
182
|
+
});
|
183
|
+
this.storage.clearTokens();
|
184
|
+
this.storage.clearStateAndVerifier();
|
185
|
+
throw new AuthError("State parameter mismatch - potential CSRF attack", ErrorCode.InvalidState);
|
186
|
+
}
|
187
|
+
if (this.storage.isTokenExpired(storedStateToken)) {
|
188
|
+
this.storage.clearTokens();
|
189
|
+
this.storage.clearStateAndVerifier();
|
190
|
+
throw new AuthError("State parameter expired - possible replay attack", ErrorCode.InvalidState);
|
191
|
+
}
|
192
|
+
if (this.oauthApi) {
|
193
|
+
try {
|
194
|
+
const response = await this.oauthApi.getTokensApiV1OauthTokenPost({
|
195
|
+
tokenRequest: {
|
196
|
+
grantType: "authorization_code",
|
197
|
+
code,
|
198
|
+
clientId: this.config.clientId,
|
199
|
+
codeVerifier: verifierToken.value,
|
200
|
+
}
|
201
|
+
});
|
202
|
+
const tokenSet = this.convertTenantTokenResponse(response);
|
203
|
+
await this.storage.setTokens(tokenSet);
|
204
|
+
this.storage.clearStateAndVerifier();
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
catch (error) {
|
208
|
+
throw new AuthError("Token exchange failed", ErrorCode.TokenExchangeFailed, error.message || "Tenant SDK request failed");
|
209
|
+
}
|
210
|
+
}
|
211
|
+
// Fallback to manual implementation
|
212
|
+
const tokenSet = await this.makeTokenRequest({
|
213
|
+
grant_type: "authorization_code",
|
214
|
+
code,
|
215
|
+
redirect_uri: this.config.redirectUri,
|
216
|
+
client_id: this.config.clientId,
|
217
|
+
code_verifier: verifierToken.value,
|
218
|
+
});
|
219
|
+
await this.storage.setTokens(tokenSet);
|
220
|
+
this.storage.clearStateAndVerifier();
|
221
|
+
}
|
222
|
+
/**
|
223
|
+
* Generates the logout URL for ending the OAuth session
|
224
|
+
*
|
225
|
+
* @returns Promise resolving to the logout URL
|
226
|
+
*
|
227
|
+
* @example
|
228
|
+
* ```typescript
|
229
|
+
* const logoutUrl = await flow.getLogoutUrl();
|
230
|
+
* // Returns: "https://auth.binoauth.com/api/v1/oauth/logout?client_id=..."
|
231
|
+
*
|
232
|
+
* window.location.href = logoutUrl; // Redirect to logout
|
233
|
+
* ```
|
234
|
+
*
|
235
|
+
* @throws {AuthError} When logout endpoint is missing from config
|
236
|
+
*/
|
237
|
+
async getLogoutUrl() {
|
238
|
+
if (!this.config.logoutEndpoint) {
|
239
|
+
throw new AuthError("Missing logoutEndpoint in config", ErrorCode.InvalidConfig);
|
240
|
+
}
|
241
|
+
const params = new URLSearchParams({
|
242
|
+
client_id: this.config.clientId,
|
243
|
+
redirect_uri: this.config.redirectUri,
|
244
|
+
});
|
245
|
+
return `${this.config.logoutEndpoint}?${params.toString()}`;
|
246
|
+
}
|
247
|
+
/**
|
248
|
+
* Generates the logout page URL with optional return URL
|
249
|
+
*
|
250
|
+
* This URL points to the BinoAuth logout page where the user can
|
251
|
+
* terminate their session and optionally be redirected back.
|
252
|
+
*
|
253
|
+
* @param returnTo - Optional URL to redirect to after logout
|
254
|
+
* @returns Promise resolving to the logout page URL
|
255
|
+
*
|
256
|
+
* @example
|
257
|
+
* ```typescript
|
258
|
+
* // Simple logout
|
259
|
+
* const logoutPageUrl = await flow.getLogoutPageUrl();
|
260
|
+
* // Returns: "https://auth.binoauth.com/auth/logout?client_id=..."
|
261
|
+
*
|
262
|
+
* // Logout with return URL
|
263
|
+
* const logoutPageUrl = await flow.getLogoutPageUrl('https://myapp.com/goodbye');
|
264
|
+
* // Returns: "https://auth.binoauth.com/auth/logout?client_id=...&return_to=..."
|
265
|
+
*
|
266
|
+
* window.location.href = logoutPageUrl;
|
267
|
+
* ```
|
268
|
+
*/
|
269
|
+
async getLogoutPageUrl(returnTo) {
|
270
|
+
const url = new URL(this.config.logoutPage);
|
271
|
+
url.searchParams.set("client_id", this.config.clientId);
|
272
|
+
if (returnTo) {
|
273
|
+
url.searchParams.set("return_to", returnTo);
|
274
|
+
}
|
275
|
+
return url.toString();
|
276
|
+
}
|
277
|
+
}
|
278
|
+
//# sourceMappingURL=authorization-code.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../../../../src/oauth/flows/authorization-code.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import type { AuthConfig, TokenSet } from "../types";
|
2
|
+
import { TokenStorage } from "../storage";
|
3
|
+
import type { OAuth2Api } from "@binoauth/tenant-sdk";
|
4
|
+
export declare abstract class BaseFlow {
|
5
|
+
protected config: AuthConfig;
|
6
|
+
protected storage: TokenStorage;
|
7
|
+
protected oauthApi?: OAuth2Api;
|
8
|
+
private rateLimiter;
|
9
|
+
protected constructor(config: AuthConfig, storage: TokenStorage, oauthApi?: OAuth2Api);
|
10
|
+
protected checkRateLimit(operation: string, maxAttempts?: number, windowMs?: number): void;
|
11
|
+
protected cleanupRateLimiter(): void;
|
12
|
+
protected makeTokenRequest(payload: Record<string, string>): Promise<TokenSet>;
|
13
|
+
private convertToTokenSet;
|
14
|
+
protected convertTenantTokenResponse(response: any): TokenSet;
|
15
|
+
protected getIssuerFromEndpoint(): string;
|
16
|
+
}
|
17
|
+
//# sourceMappingURL=base-flow.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"base-flow.d.ts","sourceRoot":"","sources":["../../../../../src/oauth/flows/base-flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,8BAAsB,QAAQ;IAC5B,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;IAC7B,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC;IAChC,SAAS,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC/B,OAAO,CAAC,WAAW,CAAgE;IAEnF,SAAS,aAAa,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,SAAS;IAMrF,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,GAAE,MAAU,EAAE,QAAQ,GAAE,MAAuB,GAAG,IAAI;IAyB7G,SAAS,CAAC,kBAAkB,IAAI,IAAI;cASpB,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8BpF,OAAO,CAAC,iBAAiB;IA4CzB,SAAS,CAAC,0BAA0B,CAAC,QAAQ,EAAE,GAAG,GAAG,QAAQ;IAiB7D,SAAS,CAAC,qBAAqB,IAAI,MAAM;CAG1C"}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { TokenStorage } from "../storage";
|
2
|
+
import { AuthError, ErrorCode } from "../error";
|
3
|
+
export class BaseFlow {
|
4
|
+
config;
|
5
|
+
storage;
|
6
|
+
oauthApi;
|
7
|
+
rateLimiter = new Map();
|
8
|
+
constructor(config, storage, oauthApi) {
|
9
|
+
this.config = config;
|
10
|
+
this.storage = storage;
|
11
|
+
this.oauthApi = oauthApi;
|
12
|
+
}
|
13
|
+
checkRateLimit(operation, maxAttempts = 5, windowMs = 15 * 60 * 1000) {
|
14
|
+
const now = Date.now();
|
15
|
+
const key = `${this.config.clientId}:${operation}`;
|
16
|
+
let limiter = this.rateLimiter.get(key);
|
17
|
+
if (!limiter || now >= limiter.resetTime) {
|
18
|
+
limiter = {
|
19
|
+
count: 0,
|
20
|
+
resetTime: now + windowMs
|
21
|
+
};
|
22
|
+
this.rateLimiter.set(key, limiter);
|
23
|
+
}
|
24
|
+
if (limiter.count >= maxAttempts) {
|
25
|
+
const timeUntilReset = Math.ceil((limiter.resetTime - now) / 1000);
|
26
|
+
throw new AuthError(`Rate limit exceeded for ${operation}. Try again in ${timeUntilReset} seconds.`, ErrorCode.RateLimitExceeded);
|
27
|
+
}
|
28
|
+
limiter.count++;
|
29
|
+
}
|
30
|
+
cleanupRateLimiter() {
|
31
|
+
const now = Date.now();
|
32
|
+
for (const [key, limiter] of this.rateLimiter.entries()) {
|
33
|
+
if (now >= limiter.resetTime) {
|
34
|
+
this.rateLimiter.delete(key);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
async makeTokenRequest(payload) {
|
39
|
+
const params = new URLSearchParams(payload);
|
40
|
+
const response = await fetch(this.config.tokenEndpoint, {
|
41
|
+
method: "POST",
|
42
|
+
headers: {
|
43
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
44
|
+
},
|
45
|
+
body: params.toString(),
|
46
|
+
});
|
47
|
+
if (!response.ok) {
|
48
|
+
let errorDetail = "Token request failed";
|
49
|
+
try {
|
50
|
+
const error = await response.json();
|
51
|
+
errorDetail = error.detail || error.error_description || error.error || errorDetail;
|
52
|
+
}
|
53
|
+
catch (e) {
|
54
|
+
errorDetail = await response.text();
|
55
|
+
}
|
56
|
+
throw new AuthError("Token request failed", ErrorCode.TokenExchangeFailed, errorDetail);
|
57
|
+
}
|
58
|
+
const tokens = await response.json();
|
59
|
+
return this.convertToTokenSet(tokens);
|
60
|
+
}
|
61
|
+
convertToTokenSet(tokens) {
|
62
|
+
if (!tokens.access_token || typeof tokens.access_token !== 'string' || tokens.access_token.trim() === '') {
|
63
|
+
throw new AuthError("Invalid token response: missing or invalid access_token", ErrorCode.TokenExchangeFailed);
|
64
|
+
}
|
65
|
+
if (tokens.expires_in === undefined || tokens.expires_in === null || typeof tokens.expires_in !== 'number' || tokens.expires_in <= 0) {
|
66
|
+
throw new AuthError("Invalid token response: missing or invalid expires_in", ErrorCode.TokenExchangeFailed);
|
67
|
+
}
|
68
|
+
if (tokens.refresh_token !== undefined && (typeof tokens.refresh_token !== 'string' || tokens.refresh_token.trim() === '')) {
|
69
|
+
throw new AuthError("Invalid token response: invalid refresh_token", ErrorCode.TokenExchangeFailed);
|
70
|
+
}
|
71
|
+
const tokenSet = {
|
72
|
+
accessToken: {
|
73
|
+
value: tokens.access_token,
|
74
|
+
expiresAt: Date.now() + tokens.expires_in * 1000,
|
75
|
+
},
|
76
|
+
};
|
77
|
+
if (tokens.refresh_token) {
|
78
|
+
tokenSet.refreshToken = {
|
79
|
+
value: tokens.refresh_token,
|
80
|
+
};
|
81
|
+
}
|
82
|
+
if (tokens.id_token) {
|
83
|
+
tokenSet.idToken = {
|
84
|
+
value: tokens.id_token,
|
85
|
+
};
|
86
|
+
}
|
87
|
+
return tokenSet;
|
88
|
+
}
|
89
|
+
convertTenantTokenResponse(response) {
|
90
|
+
const tokenSet = {
|
91
|
+
accessToken: {
|
92
|
+
value: response.accessToken,
|
93
|
+
expiresAt: Date.now() + response.expiresIn * 1000,
|
94
|
+
},
|
95
|
+
};
|
96
|
+
if (response.refreshToken) {
|
97
|
+
tokenSet.refreshToken = {
|
98
|
+
value: response.refreshToken,
|
99
|
+
};
|
100
|
+
}
|
101
|
+
return tokenSet;
|
102
|
+
}
|
103
|
+
getIssuerFromEndpoint() {
|
104
|
+
return this.config.authorizeEndpoint.replace('/api/v1/oauth/authorize', '');
|
105
|
+
}
|
106
|
+
}
|
107
|
+
//# sourceMappingURL=base-flow.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"base-flow.js","sourceRoot":"","sources":["../../../../../src/oauth/flows/base-flow.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import type { AuthConfig } from "../types";
|
2
|
+
import { TokenStorage } from "../storage";
|
3
|
+
import { BaseFlow } from "./base-flow";
|
4
|
+
import type { OAuth2Api } from "@binoauth/tenant-sdk";
|
5
|
+
/**
|
6
|
+
* OAuth 2.0 Client Credentials Grant
|
7
|
+
*
|
8
|
+
* This flow is used for server-to-server authentication where the client
|
9
|
+
* can securely store a client secret. No user interaction is required.
|
10
|
+
*
|
11
|
+
* @example
|
12
|
+
* ```typescript
|
13
|
+
* const flow = new ClientCredentialsFlow(
|
14
|
+
* { ...config, clientSecret: 'your_client_secret' },
|
15
|
+
* storage,
|
16
|
+
* oauthApi
|
17
|
+
* );
|
18
|
+
*
|
19
|
+
* // Get access token for server-to-server communication
|
20
|
+
* await flow.getTokens();
|
21
|
+
*
|
22
|
+
* // Token is now stored and ready for API calls
|
23
|
+
* const accessToken = await storage.getAccessToken();
|
24
|
+
*
|
25
|
+
* // Use token for API requests
|
26
|
+
* const response = await fetch('/api/protected', {
|
27
|
+
* headers: {
|
28
|
+
* 'Authorization': `Bearer ${accessToken.value}`
|
29
|
+
* }
|
30
|
+
* });
|
31
|
+
* ```
|
32
|
+
*/
|
33
|
+
export declare class ClientCredentialsFlow extends BaseFlow {
|
34
|
+
private clientSecret;
|
35
|
+
constructor(config: AuthConfig & {
|
36
|
+
clientSecret: string;
|
37
|
+
}, storage: TokenStorage, oauthApi?: OAuth2Api);
|
38
|
+
/**
|
39
|
+
* Requests access tokens using client credentials
|
40
|
+
*
|
41
|
+
* This method authenticates the client application itself (not a user)
|
42
|
+
* and obtains an access token for server-to-server communication.
|
43
|
+
*
|
44
|
+
* @example
|
45
|
+
* ```typescript
|
46
|
+
* const flow = new ClientCredentialsFlow(
|
47
|
+
* {
|
48
|
+
* ...config,
|
49
|
+
* clientSecret: 'your_client_secret'
|
50
|
+
* },
|
51
|
+
* storage,
|
52
|
+
* oauthApi
|
53
|
+
* );
|
54
|
+
*
|
55
|
+
* // Request tokens for server-to-server auth
|
56
|
+
* await flow.getTokens();
|
57
|
+
*
|
58
|
+
* // Use the stored token for API calls
|
59
|
+
* const accessToken = await storage.getAccessToken();
|
60
|
+
* const response = await fetch('/api/protected-resource', {
|
61
|
+
* headers: {
|
62
|
+
* 'Authorization': `Bearer ${accessToken.value}`
|
63
|
+
* }
|
64
|
+
* });
|
65
|
+
* ```
|
66
|
+
*
|
67
|
+
* @throws {AuthError} When client credentials are invalid
|
68
|
+
* @throws {AuthError} When the token request fails
|
69
|
+
*/
|
70
|
+
getTokens(): Promise<void>;
|
71
|
+
}
|
72
|
+
//# sourceMappingURL=client-credentials.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../../../../src/oauth/flows/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,EAAE,UAAU,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,SAAS;IAKtG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAoCjC"}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import { TokenStorage } from "../storage";
|
2
|
+
import { AuthError, ErrorCode } from "../error";
|
3
|
+
import { BaseFlow } from "./base-flow";
|
4
|
+
/**
|
5
|
+
* OAuth 2.0 Client Credentials Grant
|
6
|
+
*
|
7
|
+
* This flow is used for server-to-server authentication where the client
|
8
|
+
* can securely store a client secret. No user interaction is required.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
* ```typescript
|
12
|
+
* const flow = new ClientCredentialsFlow(
|
13
|
+
* { ...config, clientSecret: 'your_client_secret' },
|
14
|
+
* storage,
|
15
|
+
* oauthApi
|
16
|
+
* );
|
17
|
+
*
|
18
|
+
* // Get access token for server-to-server communication
|
19
|
+
* await flow.getTokens();
|
20
|
+
*
|
21
|
+
* // Token is now stored and ready for API calls
|
22
|
+
* const accessToken = await storage.getAccessToken();
|
23
|
+
*
|
24
|
+
* // Use token for API requests
|
25
|
+
* const response = await fetch('/api/protected', {
|
26
|
+
* headers: {
|
27
|
+
* 'Authorization': `Bearer ${accessToken.value}`
|
28
|
+
* }
|
29
|
+
* });
|
30
|
+
* ```
|
31
|
+
*/
|
32
|
+
export class ClientCredentialsFlow extends BaseFlow {
|
33
|
+
clientSecret;
|
34
|
+
constructor(config, storage, oauthApi) {
|
35
|
+
super(config, storage, oauthApi);
|
36
|
+
this.clientSecret = config.clientSecret;
|
37
|
+
}
|
38
|
+
/**
|
39
|
+
* Requests access tokens using client credentials
|
40
|
+
*
|
41
|
+
* This method authenticates the client application itself (not a user)
|
42
|
+
* and obtains an access token for server-to-server communication.
|
43
|
+
*
|
44
|
+
* @example
|
45
|
+
* ```typescript
|
46
|
+
* const flow = new ClientCredentialsFlow(
|
47
|
+
* {
|
48
|
+
* ...config,
|
49
|
+
* clientSecret: 'your_client_secret'
|
50
|
+
* },
|
51
|
+
* storage,
|
52
|
+
* oauthApi
|
53
|
+
* );
|
54
|
+
*
|
55
|
+
* // Request tokens for server-to-server auth
|
56
|
+
* await flow.getTokens();
|
57
|
+
*
|
58
|
+
* // Use the stored token for API calls
|
59
|
+
* const accessToken = await storage.getAccessToken();
|
60
|
+
* const response = await fetch('/api/protected-resource', {
|
61
|
+
* headers: {
|
62
|
+
* 'Authorization': `Bearer ${accessToken.value}`
|
63
|
+
* }
|
64
|
+
* });
|
65
|
+
* ```
|
66
|
+
*
|
67
|
+
* @throws {AuthError} When client credentials are invalid
|
68
|
+
* @throws {AuthError} When the token request fails
|
69
|
+
*/
|
70
|
+
async getTokens() {
|
71
|
+
this.checkRateLimit("client_credentials", 5, 10 * 60 * 1000);
|
72
|
+
if (this.oauthApi) {
|
73
|
+
try {
|
74
|
+
const response = await this.oauthApi.getTokensApiV1OauthTokenPost({
|
75
|
+
tokenRequest: {
|
76
|
+
grantType: "client_credentials",
|
77
|
+
clientId: this.config.clientId,
|
78
|
+
clientSecret: this.clientSecret,
|
79
|
+
scope: this.config.scope,
|
80
|
+
}
|
81
|
+
});
|
82
|
+
const tokenSet = this.convertTenantTokenResponse(response);
|
83
|
+
await this.storage.setTokens(tokenSet);
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
catch (error) {
|
87
|
+
throw new AuthError("Client credentials token request failed", ErrorCode.TokenExchangeFailed, error.message || "Tenant SDK request failed");
|
88
|
+
}
|
89
|
+
}
|
90
|
+
// Fallback to manual implementation
|
91
|
+
const tokenSet = await this.makeTokenRequest({
|
92
|
+
grant_type: "client_credentials",
|
93
|
+
client_id: this.config.clientId,
|
94
|
+
client_secret: this.clientSecret,
|
95
|
+
scope: this.config.scope,
|
96
|
+
});
|
97
|
+
await this.storage.setTokens(tokenSet);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
//# sourceMappingURL=client-credentials.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../../../../src/oauth/flows/client-credentials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;IACzC,YAAY,CAAS;IAE7B,YAAY,MAA6C,EAAE,OAAqB,EAAE,QAAoB;QACpG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IAC1C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAE7D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,4BAA4B,CAAC;oBAChE,YAAY,EAAE;wBACZ,SAAS,EAAE,oBAAoB;wBAC/B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;wBAC9B,YAAY,EAAE,IAAI,CAAC,YAAY;wBAC/B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;qBACzB;iBACF,CAAC,CAAC;gBAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAC;gBAC3D,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBACvC,OAAO;YACT,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,MAAM,IAAI,SAAS,CACjB,yCAAyC,EACzC,SAAS,CAAC,mBAAmB,EAC7B,KAAK,CAAC,OAAO,IAAI,2BAA2B,CAC7C,CAAC;YACJ,CAAC;QACH,CAAC;QAED,oCAAoC;QACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC;YAC3C,UAAU,EAAE,oBAAoB;YAChC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC/B,aAAa,EAAE,IAAI,CAAC,YAAY;YAChC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;SACzB,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;CACF"}
|