Skip to content

Commit 381a183

Browse files
committed
fix(login): handle missing expires_in in OAuth token response
The portal's legacy OAuth token endpoint does not return expires_in, causing `new Date(Date.now() + undefined * 1000).toISOString()` to throw "Invalid time value" on every login attempt. - Make LoginResponse.expires_in optional - Only set AuthConfig.expiresAt when server returns a valid duration - Display "never (long-lived token)" when no expiry - Bump version to 0.4.1 Also: fix pre-existing TS error in api.ts crawlersList return type.
1 parent 02aeba7 commit 381a183

4 files changed

Lines changed: 14 additions & 6 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@quantcdn/quant-cloud-cli",
3-
"version": "0.4.0",
3+
"version": "0.4.1",
44
"description": "Command-line interface for Quant Cloud Platform integration and management",
55
"type": "module",
66
"main": "dist/index.js",

‎src/commands/login.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,15 @@ async function handleLogin(options: LoginOptions) {
175175
spinner.text = 'Loading organization data...';
176176
const userInfo = await getUserInfo(tokenData.access_token, service.host);
177177

178-
// Save authentication config
178+
// Save authentication config.
179+
// expires_in may be missing from the OAuth response (legacy non-expiring
180+
// tokens) — only set expiresAt when the server returns a valid duration.
179181
const authConfig: AuthConfig = {
180182
token: tokenData.access_token,
181183
refreshToken: tokenData.refresh_token,
182-
expiresAt: new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString(),
184+
expiresAt: typeof tokenData.expires_in === "number"
185+
? new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString()
186+
: undefined,
183187
email: userInfo.email,
184188
host: service.host,
185189
organizations: userInfo.organizations,
@@ -204,7 +208,11 @@ async function handleLogin(options: LoginOptions) {
204208
logger.info(`${chalk.green('Platform:')} ${chalk.cyan(service.name.split(' - ')[0])}`);
205209
logger.info(`${chalk.green('Logged in as:')} ${chalk.cyan(userInfo.name)} (${chalk.cyan(userInfo.email)})`);
206210
logger.info(`${chalk.green('Access token:')} ${chalk.gray(tokenData.access_token.substring(0, 20) + '...')}`);
207-
logger.info(`${chalk.green('Expires in:')} ${chalk.cyan(Math.floor(tokenData.expires_in / 86400) + ' days')}`);
211+
if (typeof tokenData.expires_in === "number") {
212+
logger.info(`${chalk.green('Expires in:')} ${chalk.cyan(Math.floor(tokenData.expires_in / 86400) + ' days')}`);
213+
} else {
214+
logger.info(`${chalk.green('Expires in:')} ${chalk.cyan('never (long-lived token)')}`);
215+
}
208216

209217
if (userInfo.organizations?.length) {
210218
logger.info(`${chalk.green('Organizations:')} ${chalk.cyan(userInfo.organizations.length.toString())}`);

‎src/types/auth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface Organization {
3636
export interface LoginResponse {
3737
access_token: string;
3838
refresh_token?: string;
39-
expires_in: number;
39+
expires_in?: number;
4040
token_type: string;
4141
user?: {
4242
id: string;

‎src/utils/api.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export class ApiClient {
253253

254254
try {
255255
const response = await this.crawlersApi.crawlersList(organizationId, projectName);
256-
return response.data;
256+
return (response.data as any) || [];
257257
} catch (error: any) {
258258
if (error.statusCode === 404 || error.response?.status === 404) {
259259
throw new Error(`Project '${projectName}' not found or has no crawlers.`);

0 commit comments

Comments
 (0)