northbase 0.1.6 → 0.1.8
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/package.json +1 -1
- package/src/northbase.mjs +27 -5
package/package.json
CHANGED
package/src/northbase.mjs
CHANGED
|
@@ -82,12 +82,15 @@ async function doRefresh(supabase, stored) {
|
|
|
82
82
|
});
|
|
83
83
|
if (error) {
|
|
84
84
|
const revoked = error.message?.includes("invalid_grant")
|
|
85
|
+
|| error.message?.includes("Refresh Token Not Found")
|
|
86
|
+
|| error.message?.includes("refresh_token_not_found")
|
|
85
87
|
|| error.error === "invalid_grant"
|
|
86
|
-
|| error.code === "invalid_grant"
|
|
88
|
+
|| error.code === "invalid_grant"
|
|
89
|
+
|| error.code === "refresh_token_not_found";
|
|
87
90
|
if (revoked) {
|
|
88
|
-
console.error("NORTHBASE session
|
|
91
|
+
console.error("NORTHBASE session token invalid — deleting session.json");
|
|
89
92
|
deleteSession();
|
|
90
|
-
throw new Error("
|
|
93
|
+
throw new Error("Refresh token is no longer valid. Run `northbase login`.");
|
|
91
94
|
}
|
|
92
95
|
// Transient error (network, rate limit, etc.) — do NOT delete session.json
|
|
93
96
|
throw new Error(`Session refresh failed (${error.message}) — session preserved, will retry next command.`);
|
|
@@ -122,13 +125,21 @@ async function getAuthenticatedClient() {
|
|
|
122
125
|
if (needsRefresh) {
|
|
123
126
|
await doRefresh(supabase, stored);
|
|
124
127
|
} else {
|
|
125
|
-
const { error } = await supabase.auth.setSession({
|
|
128
|
+
const { data: setData, error } = await supabase.auth.setSession({
|
|
126
129
|
access_token: stored.access_token,
|
|
127
130
|
refresh_token: stored.refresh_token,
|
|
128
131
|
});
|
|
129
132
|
if (error) {
|
|
130
133
|
debug(`setSession failed (${error.message}) — falling back to refresh`);
|
|
131
134
|
await doRefresh(supabase, stored);
|
|
135
|
+
} else if (setData?.session && setData.session.access_token !== stored.access_token) {
|
|
136
|
+
// setSession decoded the JWT, found it expired, and internally called _callRefreshToken().
|
|
137
|
+
// The new tokens are returned in setData.session but NOT written to disk
|
|
138
|
+
// (persistSession: false suppresses supabase-js's internal _saveSession).
|
|
139
|
+
// Persist them now — otherwise session.json keeps the consumed refresh_token
|
|
140
|
+
// and the next command fails with "Refresh Token Not Found".
|
|
141
|
+
console.error("NORTHBASE setSession triggered internal refresh — persisting new session");
|
|
142
|
+
saveSession(setData.session);
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
|
|
@@ -236,7 +247,7 @@ async function putFile(rel, content) {
|
|
|
236
247
|
|
|
237
248
|
const { error } = await supabase
|
|
238
249
|
.from("files")
|
|
239
|
-
.upsert({ path: relSafe, content }, { onConflict: "path" });
|
|
250
|
+
.upsert({ path: relSafe, content }, { onConflict: "owner_id,path" });
|
|
240
251
|
if (error) throw error;
|
|
241
252
|
|
|
242
253
|
const updated_at = await fetchRemoteUpdatedAt(supabase, relSafe);
|
|
@@ -435,17 +446,28 @@ async function cmdAuthDebug() {
|
|
|
435
446
|
|
|
436
447
|
console.log(`has_access_token=${!!stored.access_token}`);
|
|
437
448
|
console.log(`has_refresh_token=${!!stored.refresh_token}`);
|
|
449
|
+
console.log(`refresh_token_prefix=${stored.refresh_token?.slice(0, 6) ?? "none"}`);
|
|
438
450
|
console.log(`expires_at=${expiresAt}`);
|
|
439
451
|
console.log(`seconds_remaining=${secsRemaining}`);
|
|
440
452
|
console.log(`will_refresh_soon=${secsRemaining <= 60}`);
|
|
441
453
|
console.log(`email=${stored.user?.email ?? "(unknown)"}`);
|
|
442
454
|
|
|
455
|
+
try {
|
|
456
|
+
const stat = fs.statSync(SESSION_PATH);
|
|
457
|
+
const mtimeSec = Math.floor(stat.mtimeMs / 1000);
|
|
458
|
+
console.log(`session_file_mtime=${mtimeSec}`);
|
|
459
|
+
console.log(`session_file_age_seconds=${nowSec - mtimeSec}`);
|
|
460
|
+
} catch { /* ignore */ }
|
|
461
|
+
|
|
443
462
|
let refreshAttempted = false, refreshSucceeded = false, userFetchSucceeded = false;
|
|
444
463
|
try {
|
|
445
464
|
const supabase = await getAuthenticatedClient();
|
|
446
465
|
const after = JSON.parse(fs.readFileSync(SESSION_PATH, "utf8"));
|
|
447
466
|
refreshAttempted = secsRemaining <= 60 || after.expires_at !== expiresAt;
|
|
448
467
|
refreshSucceeded = !!after.access_token;
|
|
468
|
+
console.log(`token_silently_refreshed=${after.access_token !== stored.access_token}`);
|
|
469
|
+
console.log(`refresh_token_rotated=${after.refresh_token?.slice(0,6) !== stored.refresh_token?.slice(0,6)}`);
|
|
470
|
+
console.log(`new_expires_at=${after.expires_at}`);
|
|
449
471
|
const { data, error } = await supabase.auth.getUser();
|
|
450
472
|
userFetchSucceeded = !error && !!data?.user;
|
|
451
473
|
} catch (e) {
|