From c4a00d9e17cd8fffd23ddf6253f8f274cbc1260f Mon Sep 17 00:00:00 2001 From: Gowtham S Date: Tue, 23 Sep 2025 13:40:53 +0530 Subject: [PATCH 1/5] feat: integrate new auth system --- .gitignore | 1 + commands/account.js | 72 ++++---- index.js | 4 +- lib/auth.js | 402 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + yarn.lock | 167 ++++++++++++++---- 6 files changed, 585 insertions(+), 63 deletions(-) create mode 100644 lib/auth.js diff --git a/.gitignore b/.gitignore index 9d2d2ba..779ece3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ foo/ *.log .invalid.json +.vscode diff --git a/commands/account.js b/commands/account.js index f49ace3..a38da3a 100644 --- a/commands/account.js +++ b/commands/account.js @@ -1,21 +1,27 @@ const { Command } = require('commander'); const { v4: uuidv4 } = require('uuid'); const CONSTANTS = require('../lib/constants'); -const fs = require('node:fs'); -const path = require('node:path'); -const os = require('node:os'); +const auth = require('../lib/auth'); const chalk = require('chalk'); const accountCommand = new Command('account'); accountCommand .description('Manage your account') - .action(() => { - const blessnetDir = path.join(os.homedir(), '.blessnet'); - const tokenPath = path.join(blessnetDir, 'auth_token'); - const isLoggedIn = fs.existsSync(tokenPath); // Check if auth_token file exists + .action(async () => { + const isLoggedIn = auth.isLoggedIn(); if (isLoggedIn) { - console.log(`You are ${chalk.green('logged in.')}`); + // Check if current JWT is valid + const validJWT = await auth.getValidJWT(); + if (validJWT) { + console.log(`You are ${chalk.green('logged in.')}`); + } else { + console.log( + `You are ${chalk.yellow( + 'logged in but token needs refresh.' + )} Please log in again.` + ); + } } else { console.log(`You are ${chalk.red('logged out.')}`); } @@ -30,29 +36,37 @@ accountCommand const checkLoginStatus = async () => { try { - const response = await fetch(`${CONSTANTS.authHost}/api/verify-activity`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - lid: guid, - clientid: CONSTANTS.blessAuthClientId - }) - }); + const response = await fetch( + `${CONSTANTS.authHost}/api/verify-activity`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + lid: guid, + clientid: CONSTANTS.blessAuthClientId + }) + } + ); const text = await response.text(); if (text.length === 0) { return; } const data = JSON.parse(text); // Parse the response text as JSON - if (data.token) { - console.log('Log in successful!'); - const blessnetDir = path.join(os.homedir(), '.blessnet'); - if (!fs.existsSync(blessnetDir)) { - fs.mkdirSync(blessnetDir); + if (data.jwt && data.refreshToken) { + console.log('Verifying JWT...'); + + // Verify JWT before storing + const isValid = await auth.verifyJWT(data.jwt); + if (!isValid) { + console.error('JWT verification failed. Please try again.'); + return; } - fs.writeFileSync(path.join(blessnetDir, 'auth_token'), data.token); + + console.log('Log in successful!'); + auth.setAuth(data.jwt, data.refreshToken); process.exit(0); } } catch (error) { @@ -71,13 +85,11 @@ accountCommand .command('logout') .description('Logout from your account') .action(() => { - const blessnetDir = path.join(os.homedir(), '.blessnet'); - const tokenPath = path.join(blessnetDir, 'auth_token'); - if (fs.existsSync(tokenPath)) { - fs.unlinkSync(tokenPath); - console.log('Logout successful. Token destroyed.'); + const success = auth.clearAuth(); + if (success) { + console.log('Logout successful. Tokens cleared.'); } else { - console.log('No token found. You are not logged in.'); + console.log('Error during logout.'); } }); diff --git a/index.js b/index.js index 1d2b228..d3ac51a 100755 --- a/index.js +++ b/index.js @@ -17,10 +17,10 @@ const { parseTomlConfig } = require('./lib/config'); // Import parseTomlConfig // checks const isLinked = require('node:fs').existsSync(require('node:path').join(__dirname, 'node_modules', '.bin')); const version = isLinked ? `${packageJson.version}-dev` : packageJson.version; +const auth = require('./lib/auth'); const blessnetDir = path.join(require('node:os').homedir(), '.blessnet'); const runtimePath = path.join(blessnetDir, 'bin', `bls-runtime${process.platform === 'win32' ? '.exe' : ''}`); -const authTokenPath = path.join(blessnetDir, 'auth_token'); -const isLoggedIn = fs.existsSync(authTokenPath); +const isLoggedIn = auth.isLoggedIn(); // commands const initCommand = require('./commands/init'); diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 0000000..3a7a175 --- /dev/null +++ b/lib/auth.js @@ -0,0 +1,402 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const os = require("node:os"); +const jwt = require("jsonwebtoken"); +const { JWK, JWS } = require("node-jose"); +const CONSTANTS = require("./constants"); + +const blessnetDir = path.join(os.homedir(), ".blessnet"); +const authPath = path.join(blessnetDir, "auth.json"); +const jwksPath = path.join(blessnetDir, "jwks.json"); + +/** + * Auth storage structure: + * { + * jwt: string, + * refreshToken: string, + * expiresAt: string (ISO date) + * } + */ + +/** + * JWKS cache structure: + * { + * keys: array, + * cachedAt: string (ISO date), + * expiresAt: string (ISO date) + * } + */ + +/** + * Get current auth data + * + * @returns + */ +function getAuth() { + try { + if (fs.existsSync(authPath)) { + const authData = JSON.parse(fs.readFileSync(authPath, "utf-8")); + return authData; + } + + return null; + } catch (error) { + console.error("Error reading auth data:", error); + return null; + } +} + +/** + * Store auth data + * + * @param {string} jwt - JWT token + * @param {string} refreshToken - Refresh token + * @param {string|null} expiresAt - ISO date string for expiration + * @returns + */ +function setAuth(jwt, refreshToken, expiresAt = null) { + try { + let computedExpiresAt = expiresAt; + if (!computedExpiresAt && jwt) { + const decoded = decodeJWT(jwt); + if (decoded?.exp) { + computedExpiresAt = new Date(decoded.exp * 1000).toISOString(); + } + } + + const authData = { + jwt, + refreshToken, + expiresAt: computedExpiresAt, + }; + + fs.writeFileSync(authPath, JSON.stringify(authData, null, 2)); + + return authData; + } catch (error) { + console.error("Error storing auth data:", error); + return null; + } +} + +/** + * Clear auth data + * + * @returns + */ +function clearAuth() { + try { + if (fs.existsSync(authPath)) { + fs.unlinkSync(authPath); + } + return true; + } catch (error) { + console.error("Error clearing auth data:", error); + return false; + } +} + +/** + * Check if user is logged in + * + * @returns + */ +function isLoggedIn() { + const auth = getAuth(); + return auth?.jwt && auth.refreshToken; +} + +/** + * Decode JWT without verification (for reading claims) + * + * @param {string} token - JWT token to decode + * @returns + */ +function decodeJWT(token) { + try { + return jwt.decode(token); + } catch (error) { + console.error("Error decoding JWT:", error); + return null; + } +} + +/** + * Check if JWT is expired + * + * @param {string} token - JWT token to check + * @param {number} bufferMinutes - Buffer time in minutes before expiration + * @returns + */ +function isJWTExpired(token, bufferMinutes = 5) { + try { + const decoded = decodeJWT(token); + if (!decoded || !decoded.exp) { + return true; // Consider invalid tokens as expired + } + + const expirationTime = decoded.exp * 1000; + const bufferTime = bufferMinutes * 60 * 1000; + const now = Date.now(); + + return expirationTime - bufferTime <= now; + } catch (error) { + console.error("Error checking JWT expiration:", error); + return true; + } +} + +/** + * Get cached JWKS + * + * @returns + */ +function getJWKS() { + try { + if (fs.existsSync(jwksPath)) { + const jwksData = JSON.parse(fs.readFileSync(jwksPath, "utf-8")); + + // Validate cache structure + if (!jwksData.keys || !Array.isArray(jwksData.keys) || !jwksData.cachedAt) { + console.warn("Invalid JWKS cache structure, ignoring cache"); + return null; + } + + const cachedAt = new Date(jwksData.cachedAt); + const expiresAt = new Date(cachedAt.getTime() + 60 * 60 * 1000); // 1 hour TTL + + // Validate dates + if (isNaN(cachedAt.getTime()) || isNaN(expiresAt.getTime())) { + console.warn("Invalid JWKS cache dates, ignoring cache"); + return null; + } + + if (new Date() < expiresAt) { + return jwksData.keys; + } + } + return null; + } catch (error) { + console.error("Error reading JWKS cache:", error); + // Try to clean up corrupted cache file + try { + if (fs.existsSync(jwksPath)) { + fs.unlinkSync(jwksPath); + } + } catch (cleanupError) { + console.error("Failed to clean up corrupted JWKS cache:", cleanupError); + } + return null; + } +} + +/** + * Cache JWKS data + * + * @param {Array} keys - Array of JWKS keys to cache + * @returns + */ +function setJWKS(keys) { + try { + const now = new Date(); + const jwksData = { + keys, + cachedAt: now.toISOString(), + expiresAt: new Date(now.getTime() + 60 * 60 * 1000).toISOString(), // 1 hour TTL + }; + + fs.writeFileSync(jwksPath, JSON.stringify(jwksData, null, 2)); + return true; + } catch (error) { + console.error("Error caching JWKS:", error); + return false; + } +} + +/** + * Fetch JWKS from server + * + * @returns + */ +async function fetchJWKS() { + try { + const response = await fetch( + `${CONSTANTS.authHost}/.well-known/jwks.json` + ); + + if (!response.ok) { + throw new Error(`Failed to fetch JWKS: ${response.status}`); + } + + const jwks = await response.json(); + + if (jwks?.keys && Array.isArray(jwks.keys)) { + setJWKS(jwks.keys); + return jwks.keys; + } + + throw new Error("Invalid JWKS format"); + } catch (error) { + console.error("Error fetching JWKS:", error); + return null; + } +} + +/** + * Get JWKS (from cache or fetch) + * + * @returns + */ +async function getValidJWKS() { + let keys = getJWKS(); + + if (!keys) { + keys = await fetchJWKS(); + } + + return keys; +} + +/** + * Verify JWT signature using JWKS + * + * @param {string} token - JWT token to verify + * @returns + */ +async function verifyJWT(token) { + try { + const decoded = jwt.decode(token, { complete: true }); + if (!decoded || !decoded.header || !decoded.header.kid) { + return false; + } + + const keys = await getValidJWKS(); + if (!keys) { + return false; + } + + // // Verify signature using the matching key + const key = keys.find((k) => k.kid === decoded.header.kid); + if (!key) { + const freshKeys = await fetchJWKS(); + if (freshKeys) { + const freshKey = freshKeys.find((k) => k.kid === decoded.header.kid); + if (!freshKey) { + return false; + } + + const jwk = await JWK.asKey(freshKey); + const result = await JWS.createVerify(jwk).verify(token); + return !!result; + } + return false; + } + + const jwk = await JWK.asKey(key); + const result = await JWS.createVerify(jwk).verify(token); + return !!result; + } catch (error) { + console.error("Error verifying JWT:", error); + return false; + } +} + +/** + * Refresh auth token using refresh token + * + * @returns + */ +async function refreshToken() { + try { + const auth = getAuth(); + if (!auth || !auth.refreshToken) { + return null; + } + + const response = await fetch( + `${CONSTANTS.authHost}/api/auth/refresh`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + refreshToken: auth.refreshToken, + }), + } + ); + + if (!response.ok) { + throw new Error(`Failed to refresh token: ${response.status}`); + } + + const data = await response.json(); + + if (data.jwt && data.refreshToken) { + const isValid = await verifyJWT(data.jwt); + if (!isValid) { + throw new Error("New JWT failed verification"); + } + + return setAuth(data.jwt, data.refreshToken); + } + + throw new Error("Invalid refresh response format"); + } catch (error) { + console.error("Error refreshing token:", error); + return null; + } +} + +/** + * Get valid JWT (refresh if needed) + * + * @returns + */ +async function getValidJWT() { + const auth = getAuth(); + if (!auth || !auth.jwt) { + return null; + } + + // Check if token is expired + if (isJWTExpired(auth.jwt)) { + const refreshed = await refreshToken(); + if (refreshed) { + return refreshed.jwt; + } + // If refresh fails, clear auth to force re-login + clearAuth(); + return null; + } + + // Verify current token + const isValid = await verifyJWT(auth.jwt); + if (!isValid) { + const refreshed = await refreshToken(); + if (refreshed) { + return refreshed.jwt; + } + // If refresh fails, clear auth to force re-login + clearAuth(); + return null; + } + + return auth.jwt; +} + +module.exports = { + getAuth, + setAuth, + clearAuth, + isLoggedIn, + decodeJWT, + isJWTExpired, + getJWKS, + setJWKS, + fetchJWKS, + getValidJWKS, + verifyJWT, + refreshToken, + getValidJWT, +}; diff --git a/package.json b/package.json index f286179..7a949db 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "commander": "12.1.0", "express": "^4.21.2", "follow-redirects": "^1.15.9", + "jsonwebtoken": "^9.0.2", "mocha": "^11.0.1", + "node-jose": "^2.2.0", "ora": "^7.0.0", "readline-sync": "^1.4.10", "simple-git": "^3.27.0", diff --git a/yarn.lock b/yarn.lock index f21e716..4a9639b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,6 +446,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -537,6 +542,11 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-layout@^1.2.0, buffer-layout@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" @@ -833,6 +843,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -890,7 +907,7 @@ es-set-tostringtag@^2.1.0: has-tostringtag "^1.0.2" hasown "^2.0.2" -es6-promise@^4.0.3: +es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -1418,11 +1435,44 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + just-extend@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== +jwa@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -1435,6 +1485,46 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -1459,6 +1549,11 @@ log-symbols@^6.0.0: chalk "^5.3.0" is-unicode-supported "^1.3.0" +long@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -1568,7 +1663,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.0.0, ms@^2.1.3: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -1618,6 +1713,11 @@ node-fetch@^3.3.2: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-forge@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-gyp-build@^4.3.0: version "4.8.4" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" @@ -1628,6 +1728,21 @@ node-gzip@^1.1.2: resolved "https://registry.yarnpkg.com/node-gzip/-/node-gzip-1.1.2.tgz#245bd171b31ce7c7f50fc4cd0ca7195534359afb" integrity sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw== +node-jose@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-jose/-/node-jose-2.2.0.tgz#b64f3225ad6bec328509a420800de597ba2bf3ed" + integrity sha512-XPCvJRr94SjLrSIm4pbYHKLEaOsDvJCpyFw/6V/KK/IXmyZ6SFBzAUDO9HQf4DB/nTEFcRGH87mNciOP23kFjw== + dependencies: + base64url "^3.0.1" + buffer "^6.0.3" + es6-promise "^4.2.8" + lodash "^4.17.21" + long "^5.2.0" + node-forge "^1.2.1" + pako "^2.0.4" + process "^0.11.10" + uuid "^9.0.0" + noop6@^1.0.1: version "1.0.10" resolved "https://registry.yarnpkg.com/noop6/-/noop6-1.0.10.tgz#beb17db586b684585eb955de502506a069c817d7" @@ -1738,7 +1853,7 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -pako@^2.0.3: +pako@^2.0.3, pako@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== @@ -1786,6 +1901,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -1905,6 +2025,11 @@ safe-regex-test@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +semver@^7.5.4: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -2066,16 +2191,7 @@ stdin-discarder@^0.2.2: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2118,14 +2234,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2291,6 +2400,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -2339,16 +2453,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From e7cf41dbd3f13fb4015647d1f91b281fd7a4c556 Mon Sep 17 00:00:00 2001 From: Gowtham S Date: Tue, 23 Sep 2025 17:11:46 +0530 Subject: [PATCH 2/5] fix: api urls --- commands/account.js | 4 +- lib/auth.js | 177 ++++++++++++++++++++++++++++---------------- lib/constants.js | 6 +- 3 files changed, 119 insertions(+), 68 deletions(-) diff --git a/commands/account.js b/commands/account.js index a38da3a..10f3c24 100644 --- a/commands/account.js +++ b/commands/account.js @@ -37,7 +37,7 @@ accountCommand const checkLoginStatus = async () => { try { const response = await fetch( - `${CONSTANTS.authHost}/api/verify-activity`, + `${CONSTANTS.authHost}/api/v1/auth/verify-activity`, { method: 'POST', headers: { @@ -48,7 +48,7 @@ accountCommand clientid: CONSTANTS.blessAuthClientId }) } - ); + ); const text = await response.text(); if (text.length === 0) { return; diff --git a/lib/auth.js b/lib/auth.js index 3a7a175..d0340da 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,13 +1,13 @@ -const fs = require("node:fs"); -const path = require("node:path"); -const os = require("node:os"); -const jwt = require("jsonwebtoken"); -const { JWK, JWS } = require("node-jose"); -const CONSTANTS = require("./constants"); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const jwt = require('jsonwebtoken'); +const { JWK, JWS } = require('node-jose'); +const CONSTANTS = require('./constants'); -const blessnetDir = path.join(os.homedir(), ".blessnet"); -const authPath = path.join(blessnetDir, "auth.json"); -const jwksPath = path.join(blessnetDir, "jwks.json"); +const blessnetDir = path.join(os.homedir(), '.blessnet'); +const authPath = path.join(blessnetDir, 'auth.json'); +const jwksPath = path.join(blessnetDir, 'jwks.json'); /** * Auth storage structure: @@ -35,13 +35,13 @@ const jwksPath = path.join(blessnetDir, "jwks.json"); function getAuth() { try { if (fs.existsSync(authPath)) { - const authData = JSON.parse(fs.readFileSync(authPath, "utf-8")); + const authData = JSON.parse(fs.readFileSync(authPath, 'utf-8')); return authData; } return null; } catch (error) { - console.error("Error reading auth data:", error); + console.error('Error reading auth data:', error); return null; } } @@ -49,9 +49,9 @@ function getAuth() { /** * Store auth data * - * @param {string} jwt - JWT token - * @param {string} refreshToken - Refresh token - * @param {string|null} expiresAt - ISO date string for expiration + * @param {string} jwt + * @param {string} refreshToken + * @param {string|null} expiresAt * @returns */ function setAuth(jwt, refreshToken, expiresAt = null) { @@ -74,7 +74,7 @@ function setAuth(jwt, refreshToken, expiresAt = null) { return authData; } catch (error) { - console.error("Error storing auth data:", error); + console.error('Error storing auth data:', error); return null; } } @@ -91,7 +91,7 @@ function clearAuth() { } return true; } catch (error) { - console.error("Error clearing auth data:", error); + console.error('Error clearing auth data:', error); return false; } } @@ -109,14 +109,14 @@ function isLoggedIn() { /** * Decode JWT without verification (for reading claims) * - * @param {string} token - JWT token to decode + * @param {string} token * @returns */ function decodeJWT(token) { try { return jwt.decode(token); } catch (error) { - console.error("Error decoding JWT:", error); + console.error('Error decoding JWT:', error); return null; } } @@ -124,8 +124,8 @@ function decodeJWT(token) { /** * Check if JWT is expired * - * @param {string} token - JWT token to check - * @param {number} bufferMinutes - Buffer time in minutes before expiration + * @param {string} token + * @param {number} bufferMinutes * @returns */ function isJWTExpired(token, bufferMinutes = 5) { @@ -141,7 +141,7 @@ function isJWTExpired(token, bufferMinutes = 5) { return expirationTime - bufferTime <= now; } catch (error) { - console.error("Error checking JWT expiration:", error); + console.error('Error checking JWT expiration:', error); return true; } } @@ -154,11 +154,15 @@ function isJWTExpired(token, bufferMinutes = 5) { function getJWKS() { try { if (fs.existsSync(jwksPath)) { - const jwksData = JSON.parse(fs.readFileSync(jwksPath, "utf-8")); + const jwksData = JSON.parse(fs.readFileSync(jwksPath, 'utf-8')); // Validate cache structure - if (!jwksData.keys || !Array.isArray(jwksData.keys) || !jwksData.cachedAt) { - console.warn("Invalid JWKS cache structure, ignoring cache"); + if ( + !jwksData.keys || + !Array.isArray(jwksData.keys) || + !jwksData.cachedAt + ) { + console.warn('Invalid JWKS cache structure, ignoring cache'); return null; } @@ -167,7 +171,7 @@ function getJWKS() { // Validate dates if (isNaN(cachedAt.getTime()) || isNaN(expiresAt.getTime())) { - console.warn("Invalid JWKS cache dates, ignoring cache"); + console.warn('Invalid JWKS cache dates, ignoring cache'); return null; } @@ -177,14 +181,14 @@ function getJWKS() { } return null; } catch (error) { - console.error("Error reading JWKS cache:", error); + console.error('Error reading JWKS cache:', error); // Try to clean up corrupted cache file try { if (fs.existsSync(jwksPath)) { fs.unlinkSync(jwksPath); } } catch (cleanupError) { - console.error("Failed to clean up corrupted JWKS cache:", cleanupError); + console.error('Failed to clean up corrupted JWKS cache:', cleanupError); } return null; } @@ -193,7 +197,7 @@ function getJWKS() { /** * Cache JWKS data * - * @param {Array} keys - Array of JWKS keys to cache + * @param {Array} keys * @returns */ function setJWKS(keys) { @@ -208,7 +212,7 @@ function setJWKS(keys) { fs.writeFileSync(jwksPath, JSON.stringify(jwksData, null, 2)); return true; } catch (error) { - console.error("Error caching JWKS:", error); + console.error('Error caching JWKS:', error); return false; } } @@ -220,9 +224,7 @@ function setJWKS(keys) { */ async function fetchJWKS() { try { - const response = await fetch( - `${CONSTANTS.authHost}/.well-known/jwks.json` - ); + const response = await fetch(`${CONSTANTS.authHost}/api/v1/.well-known/jwks.json`); if (!response.ok) { throw new Error(`Failed to fetch JWKS: ${response.status}`); @@ -235,9 +237,9 @@ async function fetchJWKS() { return jwks.keys; } - throw new Error("Invalid JWKS format"); + throw new Error('Invalid JWKS format'); } catch (error) { - console.error("Error fetching JWKS:", error); + console.error('Error fetching JWKS:', error); return null; } } @@ -260,7 +262,7 @@ async function getValidJWKS() { /** * Verify JWT signature using JWKS * - * @param {string} token - JWT token to verify + * @param {string} token * @returns */ async function verifyJWT(token) { @@ -296,7 +298,7 @@ async function verifyJWT(token) { const result = await JWS.createVerify(jwk).verify(token); return !!result; } catch (error) { - console.error("Error verifying JWT:", error); + console.error('Error verifying JWT:', error); return false; } } @@ -313,18 +315,15 @@ async function refreshToken() { return null; } - const response = await fetch( - `${CONSTANTS.authHost}/api/auth/refresh`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - refreshToken: auth.refreshToken, - }), - } - ); + const response = await fetch(`${CONSTANTS.authHost}/api/v1/auth/refresh`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + refreshToken: auth.refreshToken, + }), + }); if (!response.ok) { throw new Error(`Failed to refresh token: ${response.status}`); @@ -335,21 +334,23 @@ async function refreshToken() { if (data.jwt && data.refreshToken) { const isValid = await verifyJWT(data.jwt); if (!isValid) { - throw new Error("New JWT failed verification"); + // Verification failed - return null to trigger re-authentication + return null; } return setAuth(data.jwt, data.refreshToken); } - throw new Error("Invalid refresh response format"); + // Invalid response format - return null + return null; } catch (error) { - console.error("Error refreshing token:", error); + console.error('Error refreshing token:', error); return null; } } /** - * Get valid JWT (refresh if needed) + * Get valid JWT, after refresh if needed * * @returns */ @@ -359,30 +360,79 @@ async function getValidJWT() { return null; } - // Check if token is expired + // Refresh token if it is expired if (isJWTExpired(auth.jwt)) { const refreshed = await refreshToken(); if (refreshed) { return refreshed.jwt; } - // If refresh fails, clear auth to force re-login + // If refresh fails, clear force re-auth clearAuth(); return null; } - // Verify current token - const isValid = await verifyJWT(auth.jwt); - if (!isValid) { + return auth.jwt; +} + +/** + * Make an authenticated fetch request with automatic token refresh + * Handles 401/403 responses by attempting token refresh and retry + * + * @param {string} url + * @param {object} options + * @returns + */ +async function authenticatedFetch(url, options = {}) { + // Get valid JWT (pre-validated) + const jwt = await getValidJWT(); + if (!jwt) { + throw new Error( + 'Authentication required. Please run: blessnet options account login' + ); + } + + // First attempt with current JWT + let response = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${jwt}`, + }, + }); + + if (response.status === 401 || response.status === 403) { + console.log('Token expired, attempting refresh...'); + + // Try refresh const refreshed = await refreshToken(); - if (refreshed) { - return refreshed.jwt; + + if (refreshed?.jwt) { + // Retry with new token + response = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${refreshed.jwt}`, + }, + }); + + // If still failing after refresh, force re-auth + if (response.status === 401 || response.status === 403) { + clearAuth(); + throw new Error( + 'Authentication failed. Please login again: blessnet options account login' + ); + } + } else { + // Refresh failed - clear auth and force re-login + clearAuth(); + throw new Error( + 'Session expired. Please login again: blessnet options account login' + ); } - // If refresh fails, clear auth to force re-login - clearAuth(); - return null; } - return auth.jwt; + return response; } module.exports = { @@ -399,4 +449,5 @@ module.exports = { verifyJWT, refreshToken, getValidJWT, + authenticatedFetch, }; diff --git a/lib/constants.js b/lib/constants.js index 16a7ed2..94990d9 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -24,13 +24,13 @@ const SOLANA_CLUSTERS = [{ }] const CONSTANTS = { - blessAuthClientId: '4f70928c9b0f88b54ee3b940019ec1bd', + blessAuthClientId: 'bless-f35sb24h', BLESNET_HOME, BLESSNET_DIR, SOLANA_CLUSTERS, - authHost: 'https://auth.bless.network' // Add this line + authHost: 'https://bless-auth-dev.bls.dev' } -module.exports = CONSTANTS; \ No newline at end of file +module.exports = CONSTANTS; From bfb3f93dc60aee36729782d3964842ccc1f71b68 Mon Sep 17 00:00:00 2001 From: Gowtham S Date: Tue, 23 Sep 2025 17:30:43 +0530 Subject: [PATCH 3/5] fix: update old manual refresh flow --- commands/account.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/commands/account.js b/commands/account.js index 10f3c24..4655b37 100644 --- a/commands/account.js +++ b/commands/account.js @@ -11,19 +11,15 @@ accountCommand .action(async () => { const isLoggedIn = auth.isLoggedIn(); if (isLoggedIn) { - // Check if current JWT is valid + // Check if authentication is valid (includes auto-refresh) const validJWT = await auth.getValidJWT(); if (validJWT) { console.log(`You are ${chalk.green('logged in.')}`); } else { - console.log( - `You are ${chalk.yellow( - 'logged in but token needs refresh.' - )} Please log in again.` - ); + console.log(`You are ${chalk.red('logged out.')} Run: ${chalk.blue('blessnet options account login')}`); } } else { - console.log(`You are ${chalk.red('logged out.')}`); + console.log(`You are ${chalk.red('logged out.')} Run: ${chalk.blue('blessnet options account login')}`); } }); From e44f172ab92bed64e1be41c1dbd8fb5e6c31b905 Mon Sep 17 00:00:00 2001 From: Gowtham S Date: Thu, 25 Sep 2025 11:40:24 +0530 Subject: [PATCH 4/5] fix: correct url params --- commands/account.js | 11 ++++++----- lib/auth.js | 2 +- test/invoke.test.js | 15 --------------- 3 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 test/invoke.test.js diff --git a/commands/account.js b/commands/account.js index 4655b37..7132c62 100644 --- a/commands/account.js +++ b/commands/account.js @@ -28,7 +28,7 @@ accountCommand .description('Log in to your account') .action(() => { const guid = uuidv4(); - console.log(`Please log in at: ${CONSTANTS.authHost}/?lid=${guid}&clientid=${CONSTANTS.blessAuthClientId}`); + console.log(`Please log in at: ${CONSTANTS.authHost}/?lid=${guid}&clientId=${CONSTANTS.blessAuthClientId}`); const checkLoginStatus = async () => { try { @@ -41,7 +41,7 @@ accountCommand }, body: JSON.stringify({ lid: guid, - clientid: CONSTANTS.blessAuthClientId + clientId: CONSTANTS.blessAuthClientId }) } ); @@ -50,10 +50,9 @@ accountCommand return; } - const data = JSON.parse(text); // Parse the response text as JSON - if (data.jwt && data.refreshToken) { - console.log('Verifying JWT...'); + const data = JSON.parse(text); + if (data.jwt && data.refreshToken) { // Verify JWT before storing const isValid = await auth.verifyJWT(data.jwt); if (!isValid) { @@ -64,6 +63,8 @@ accountCommand console.log('Log in successful!'); auth.setAuth(data.jwt, data.refreshToken); process.exit(0); + } else { + console.log('Response missing jwt or refreshToken'); } } catch (error) { console.error('Error checking log in status:', error); diff --git a/lib/auth.js b/lib/auth.js index d0340da..d097bc0 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -277,7 +277,7 @@ async function verifyJWT(token) { return false; } - // // Verify signature using the matching key + // Verify signature using the matching key const key = keys.find((k) => k.kid === decoded.header.kid); if (!key) { const freshKeys = await fetchJWKS(); diff --git a/test/invoke.test.js b/test/invoke.test.js deleted file mode 100644 index eb50ad5..0000000 --- a/test/invoke.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const { run } = require('../lib/invoke'); -const path = require('node:path'); -const assert = require('assert'); - -describe('run', () => { - it('should return "hello world!" for the wasm file', async () => { - const options = { - wasmPath: path.join(__dirname, '../fixtures/hello_test.wasm'), - env: ['TEST_ENV=1'] - }; - const result = await run(options); - assert.strictEqual(result.trim(), 'hello world!'); - }); -}); - From a0a940f202e97a51a353db34a4825a85bb7b0362 Mon Sep 17 00:00:00 2001 From: Gowtham S Date: Thu, 25 Sep 2025 11:49:56 +0530 Subject: [PATCH 5/5] fix: revert test removal --- test/invoke.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/invoke.test.js diff --git a/test/invoke.test.js b/test/invoke.test.js new file mode 100644 index 0000000..a7272d6 --- /dev/null +++ b/test/invoke.test.js @@ -0,0 +1,14 @@ +const { run } = require('../lib/invoke'); +const path = require('node:path'); +const assert = require('assert'); + +describe('run', () => { + it('should return "hello world!" for the wasm file', async () => { + const options = { + wasmPath: path.join(__dirname, '../fixtures/hello_test.wasm'), + env: ['TEST_ENV=1'] + }; + const result = await run(options); + assert.strictEqual(result.trim(), 'hello world!'); + }); +});