@insforge/sdk 1.1.0 → 1.1.2-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +69 -2
- package/dist/index.d.ts +69 -2
- package/dist/index.js +179 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +179 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -318,21 +318,74 @@ function isHostedAuthEnvironment() {
|
|
|
318
318
|
}
|
|
319
319
|
return false;
|
|
320
320
|
}
|
|
321
|
+
var PKCE_VERIFIER_KEY = "insforge_pkce_verifier";
|
|
322
|
+
function generateCodeVerifier() {
|
|
323
|
+
const array = new Uint8Array(32);
|
|
324
|
+
crypto.getRandomValues(array);
|
|
325
|
+
return base64UrlEncode(array);
|
|
326
|
+
}
|
|
327
|
+
async function generateCodeChallenge(verifier) {
|
|
328
|
+
const encoder = new TextEncoder();
|
|
329
|
+
const data = encoder.encode(verifier);
|
|
330
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
331
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
332
|
+
}
|
|
333
|
+
function base64UrlEncode(buffer) {
|
|
334
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
335
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
336
|
+
}
|
|
337
|
+
function storePkceVerifier(verifier) {
|
|
338
|
+
if (typeof sessionStorage !== "undefined") {
|
|
339
|
+
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function retrievePkceVerifier() {
|
|
343
|
+
if (typeof sessionStorage === "undefined") {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
347
|
+
if (verifier) {
|
|
348
|
+
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
349
|
+
}
|
|
350
|
+
return verifier;
|
|
351
|
+
}
|
|
321
352
|
var Auth = class {
|
|
322
353
|
constructor(http, tokenManager) {
|
|
323
354
|
this.http = http;
|
|
324
355
|
this.tokenManager = tokenManager;
|
|
325
|
-
this.detectAuthCallback();
|
|
356
|
+
this.authCallbackHandled = this.detectAuthCallback();
|
|
326
357
|
}
|
|
327
358
|
/**
|
|
328
359
|
* Automatically detect and handle OAuth callback parameters in the URL
|
|
329
360
|
* This runs after initialization to seamlessly complete the OAuth flow
|
|
330
|
-
*
|
|
361
|
+
*
|
|
362
|
+
* Supports two flows:
|
|
363
|
+
* - PKCE flow (new): Backend returns `insforge_code` param, exchanged for tokens
|
|
364
|
+
* - Legacy flow: Backend returns tokens directly in URL (backward compatible)
|
|
331
365
|
*/
|
|
332
|
-
detectAuthCallback() {
|
|
366
|
+
async detectAuthCallback() {
|
|
333
367
|
if (typeof window === "undefined") return;
|
|
334
368
|
try {
|
|
335
369
|
const params = new URLSearchParams(window.location.search);
|
|
370
|
+
const error = params.get("error");
|
|
371
|
+
if (error) {
|
|
372
|
+
const url = new URL(window.location.href);
|
|
373
|
+
url.searchParams.delete("error");
|
|
374
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
375
|
+
console.debug("OAuth callback error:", error);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const code = params.get("insforge_code");
|
|
379
|
+
if (code) {
|
|
380
|
+
const url = new URL(window.location.href);
|
|
381
|
+
url.searchParams.delete("insforge_code");
|
|
382
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
383
|
+
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
384
|
+
if (exchangeError) {
|
|
385
|
+
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
336
389
|
const accessToken = params.get("access_token");
|
|
337
390
|
const userId = params.get("user_id");
|
|
338
391
|
const email = params.get("email");
|
|
@@ -350,8 +403,6 @@ var Auth = class {
|
|
|
350
403
|
email,
|
|
351
404
|
profile: { name: name || "" },
|
|
352
405
|
metadata: null,
|
|
353
|
-
// These fields are not provided by backend OAuth callback
|
|
354
|
-
// They'll be populated when calling getCurrentUser()
|
|
355
406
|
emailVerified: false,
|
|
356
407
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
408
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -365,9 +416,6 @@ var Auth = class {
|
|
|
365
416
|
url.searchParams.delete("email");
|
|
366
417
|
url.searchParams.delete("name");
|
|
367
418
|
url.searchParams.delete("csrf_token");
|
|
368
|
-
if (params.has("error")) {
|
|
369
|
-
url.searchParams.delete("error");
|
|
370
|
-
}
|
|
371
419
|
window.history.replaceState({}, document.title, url.toString());
|
|
372
420
|
}
|
|
373
421
|
} catch (error) {
|
|
@@ -448,11 +496,20 @@ var Auth = class {
|
|
|
448
496
|
}
|
|
449
497
|
/**
|
|
450
498
|
* Sign in with OAuth provider
|
|
499
|
+
* Uses PKCE (Proof Key for Code Exchange) for enhanced security
|
|
451
500
|
*/
|
|
452
501
|
async signInWithOAuth(options) {
|
|
453
502
|
try {
|
|
454
503
|
const { provider, redirectTo, skipBrowserRedirect } = options;
|
|
455
|
-
const
|
|
504
|
+
const codeVerifier = generateCodeVerifier();
|
|
505
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
506
|
+
storePkceVerifier(codeVerifier);
|
|
507
|
+
const params = {
|
|
508
|
+
code_challenge: codeChallenge
|
|
509
|
+
};
|
|
510
|
+
if (redirectTo) {
|
|
511
|
+
params.redirect_uri = redirectTo;
|
|
512
|
+
}
|
|
456
513
|
const endpoint = `/api/auth/oauth/${provider}`;
|
|
457
514
|
const response = await this.http.get(endpoint, { params });
|
|
458
515
|
if (typeof window !== "undefined" && !skipBrowserRedirect) {
|
|
@@ -462,7 +519,9 @@ var Auth = class {
|
|
|
462
519
|
return {
|
|
463
520
|
data: {
|
|
464
521
|
url: response.authUrl,
|
|
465
|
-
provider
|
|
522
|
+
provider,
|
|
523
|
+
codeVerifier
|
|
524
|
+
// Return verifier for manual flow (skipBrowserRedirect)
|
|
466
525
|
},
|
|
467
526
|
error: null
|
|
468
527
|
};
|
|
@@ -480,6 +539,85 @@ var Auth = class {
|
|
|
480
539
|
};
|
|
481
540
|
}
|
|
482
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Exchange OAuth authorization code for tokens (PKCE flow)
|
|
544
|
+
*
|
|
545
|
+
* After OAuth callback redirects with an `insforge_code` parameter, call this method
|
|
546
|
+
* to exchange it for access tokens. The code verifier is automatically
|
|
547
|
+
* retrieved from sessionStorage if available.
|
|
548
|
+
*
|
|
549
|
+
* Note: This is called automatically by the SDK on initialization. You typically
|
|
550
|
+
* don't need to call this directly unless using `skipBrowserRedirect: true`.
|
|
551
|
+
*
|
|
552
|
+
* @param code - The authorization code from OAuth callback URL
|
|
553
|
+
* @param codeVerifier - Optional code verifier (auto-retrieved from sessionStorage if not provided)
|
|
554
|
+
* @returns Session data with access token and user info
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* // Automatic verifier retrieval (recommended for browser)
|
|
559
|
+
* const params = new URLSearchParams(window.location.search);
|
|
560
|
+
* const code = params.get('insforge_code');
|
|
561
|
+
* if (code) {
|
|
562
|
+
* const { data, error } = await insforge.auth.exchangeOAuthCode(code);
|
|
563
|
+
* }
|
|
564
|
+
*
|
|
565
|
+
* // Manual verifier (for custom flows)
|
|
566
|
+
* const { data, error } = await insforge.auth.exchangeOAuthCode(code, codeVerifier);
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
async exchangeOAuthCode(code, codeVerifier) {
|
|
570
|
+
try {
|
|
571
|
+
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
572
|
+
if (!verifier) {
|
|
573
|
+
return {
|
|
574
|
+
data: null,
|
|
575
|
+
error: new InsForgeError(
|
|
576
|
+
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
577
|
+
400,
|
|
578
|
+
"PKCE_VERIFIER_MISSING"
|
|
579
|
+
)
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const request = {
|
|
583
|
+
code,
|
|
584
|
+
code_verifier: verifier
|
|
585
|
+
};
|
|
586
|
+
const response = await this.http.post("/api/auth/oauth/exchange", request, { credentials: "include" });
|
|
587
|
+
if (!isHostedAuthEnvironment()) {
|
|
588
|
+
const session = {
|
|
589
|
+
accessToken: response.accessToken,
|
|
590
|
+
user: response.user
|
|
591
|
+
};
|
|
592
|
+
if (response.csrfToken) {
|
|
593
|
+
this.tokenManager.setMemoryMode();
|
|
594
|
+
setCsrfToken(response.csrfToken);
|
|
595
|
+
}
|
|
596
|
+
this.tokenManager.saveSession(session);
|
|
597
|
+
this.http.setAuthToken(response.accessToken);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
data: {
|
|
601
|
+
accessToken: response.accessToken,
|
|
602
|
+
user: response.user,
|
|
603
|
+
redirectTo: response.redirectTo
|
|
604
|
+
},
|
|
605
|
+
error: null
|
|
606
|
+
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error instanceof InsForgeError) {
|
|
609
|
+
return { data: null, error };
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
data: null,
|
|
613
|
+
error: new InsForgeError(
|
|
614
|
+
"An unexpected error occurred during OAuth code exchange",
|
|
615
|
+
500,
|
|
616
|
+
"UNEXPECTED_ERROR"
|
|
617
|
+
)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
483
621
|
/**
|
|
484
622
|
* Sign out the current user
|
|
485
623
|
*/
|
|
@@ -568,8 +706,10 @@ var Auth = class {
|
|
|
568
706
|
/**
|
|
569
707
|
* Get the current session (only session data, no API call)
|
|
570
708
|
* Returns the stored JWT token and basic user info from local storage
|
|
709
|
+
* Automatically waits for any pending OAuth callback to complete first
|
|
571
710
|
*/
|
|
572
711
|
async getCurrentSession() {
|
|
712
|
+
await this.authCallbackHandled;
|
|
573
713
|
if (isHostedAuthEnvironment()) {
|
|
574
714
|
return { data: { session: null }, error: null };
|
|
575
715
|
}
|
|
@@ -577,6 +717,11 @@ var Auth = class {
|
|
|
577
717
|
const session = this.tokenManager.getSession();
|
|
578
718
|
if (session) {
|
|
579
719
|
this.http.setAuthToken(session.accessToken);
|
|
720
|
+
if (!session.user) {
|
|
721
|
+
const authResponse = await this.http.get("/api/auth/sessions/current", { credentials: "include" });
|
|
722
|
+
session.user = authResponse.user;
|
|
723
|
+
this.tokenManager.setUser(session.user);
|
|
724
|
+
}
|
|
580
725
|
return { data: { session }, error: null };
|
|
581
726
|
}
|
|
582
727
|
if (typeof window !== "undefined") {
|
|
@@ -868,8 +1013,10 @@ function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
|
868
1013
|
return async (input, init) => {
|
|
869
1014
|
const url = typeof input === "string" ? input : input.toString();
|
|
870
1015
|
const urlObj = new URL(url);
|
|
871
|
-
const
|
|
872
|
-
const
|
|
1016
|
+
const pathname = urlObj.pathname.slice(1);
|
|
1017
|
+
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
1018
|
+
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
1019
|
+
const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
873
1020
|
const token = tokenManager.getAccessToken();
|
|
874
1021
|
const httpHeaders = httpClient.getHeaders();
|
|
875
1022
|
const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
|
|
@@ -929,6 +1076,25 @@ var Database = class {
|
|
|
929
1076
|
from(table) {
|
|
930
1077
|
return this.postgrest.from(table);
|
|
931
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Call a PostgreSQL function (RPC)
|
|
1081
|
+
*
|
|
1082
|
+
* @example
|
|
1083
|
+
* // Call a function with parameters
|
|
1084
|
+
* const { data, error } = await client.database
|
|
1085
|
+
* .rpc('get_user_stats', { user_id: 123 });
|
|
1086
|
+
*
|
|
1087
|
+
* // Call a function with no parameters
|
|
1088
|
+
* const { data, error } = await client.database
|
|
1089
|
+
* .rpc('get_all_active_users');
|
|
1090
|
+
*
|
|
1091
|
+
* // With options (head, count, get)
|
|
1092
|
+
* const { data, count } = await client.database
|
|
1093
|
+
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
1094
|
+
*/
|
|
1095
|
+
rpc(fn, args, options) {
|
|
1096
|
+
return this.postgrest.rpc(fn, args, options);
|
|
1097
|
+
}
|
|
932
1098
|
};
|
|
933
1099
|
|
|
934
1100
|
// src/modules/storage.ts
|
|
@@ -1734,8 +1900,7 @@ var InsForgeClient = class {
|
|
|
1734
1900
|
this.http.setAuthToken(config.edgeFunctionToken);
|
|
1735
1901
|
this.tokenManager.saveSession({
|
|
1736
1902
|
accessToken: config.edgeFunctionToken,
|
|
1737
|
-
user:
|
|
1738
|
-
// Will be populated by getCurrentUser()
|
|
1903
|
+
user: null
|
|
1739
1904
|
});
|
|
1740
1905
|
}
|
|
1741
1906
|
const existingSession = this.tokenManager.getSession();
|