Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
"/oclif.manifest.json"
],
"dependencies": {
"@inquirer/prompts": "7.8.4",
"@inquirer/prompts": "7.8.6",
"@internxt/inxt-js": "2.2.7",
"@internxt/lib": "1.3.1",
"@internxt/sdk": "1.11.1",
"@oclif/core": "4.5.2",
"@internxt/sdk": "1.11.9",
"@oclif/core": "4.5.3",
"@oclif/plugin-autocomplete": "3.2.34",
"axios": "1.11.0",
"axios": "1.12.2",
"bip39": "3.1.0",
"body-parser": "2.2.0",
"cli-progress": "3.12.0",
Expand All @@ -53,7 +53,8 @@
"fast-xml-parser": "5.2.5",
"mime-types": "3.0.1",
"openpgp": "6.2.2",
"pm2": "6.0.10",
"otpauth": "9.4.1",
"pm2": "6.0.11",
"range-parser": "1.2.1",
"selfsigned": "3.0.1",
"tty-table": "4.2.3",
Expand All @@ -66,15 +67,15 @@
"@types/cli-progress": "3.11.6",
"@types/express": "5.0.3",
"@types/mime-types": "3.0.1",
"@types/node": "22.16.0",
"@types/node": "22.18.3",
"@types/range-parser": "1.2.7",
"@vitest/coverage-istanbul": "3.2.4",
"@vitest/spy": "3.2.4",
"eslint": "9.34.0",
"eslint": "9.35.0",
"husky": "9.1.7",
"lint-staged": "16.1.6",
"nodemon": "3.1.10",
"oclif": "4.22.16",
"oclif": "4.22.22",
"prettier": "3.6.2",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
Expand Down
6 changes: 6 additions & 0 deletions src/commands/download-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ export default class DownloadFile extends Command {

await executeDownload;

try {
await fs.utimes(downloadPath, new Date(), driveFile.modificationTime ?? driveFile.updatedAt);
} catch {
/* noop */
}

progressBar?.update(100);
progressBar?.stop();
const message = `File downloaded successfully to ${downloadPath}`;
Expand Down
5 changes: 1 addition & 4 deletions src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export default class Login extends Command {

const loginCredentials = await AuthService.instance.doLogin(email, password, twoFactorCode);

SdkManager.init({
token: loginCredentials.token,
newToken: loginCredentials.newToken,
});
SdkManager.init({ token: loginCredentials.token });

await ConfigService.instance.saveUser(loginCredentials);
const message = `Succesfully logged in to: ${loginCredentials.user.email}`;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/move-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class MoveFile extends Command {
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const newFile = await DriveFileService.instance.moveFile({ fileUuid, destinationFolderUuid });
const newFile = await DriveFileService.instance.moveFile(fileUuid, { destinationFolder: destinationFolderUuid });
const message = `File moved successfully to: ${destinationFolderUuid}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, file: newFile };
Expand Down
4 changes: 3 additions & 1 deletion src/commands/move-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export default class MoveFolder extends Command {
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const newFolder = await DriveFolderService.instance.moveFolder({ folderUuid, destinationFolderUuid });
const newFolder = await DriveFolderService.instance.moveFolder(folderUuid, {
destinationFolder: destinationFolderUuid,
});
const message = `Folder moved successfully to: ${destinationFolderUuid}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, folder: newFolder };
Expand Down
13 changes: 9 additions & 4 deletions src/commands/rename-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CLIUtils } from '../utils/cli.utils';
import { EmptyFileNameError, MissingCredentialsError, NotValidFileUuidError } from '../types/command.types';
import { ValidationService } from '../services/validation.service';
import { DriveFileService } from '../services/drive/drive-file.service';
import path from 'node:path';

export default class RenameFile extends Command {
static readonly args = {};
Expand Down Expand Up @@ -33,12 +34,16 @@ export default class RenameFile extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const fileUuid = await this.getFileUuid(flags['id'], nonInteractive);
const newName = await this.getFileName(flags['name'], nonInteractive);
const fileName = await this.getFileName(flags['name'], nonInteractive);

await DriveFileService.instance.renameFile(fileUuid, { plainName: newName });
const message = `File renamed successfully with: ${newName}`;
const pathInfo = path.parse(fileName);
const newName = pathInfo.name;
const newType = pathInfo.ext.replace('.', '');

await DriveFileService.instance.renameFile(fileUuid, { plainName: newName, type: newType });
const message = `File renamed successfully with: ${newName}${newType ? '.' + newType : ''}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, file: { uuid: fileUuid, plainName: newName } };
return { success: true, message, file: { uuid: fileUuid, plainName: newName, type: newType } };
};

public catch = async (error: Error) => {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/trash-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class TrashFile extends Command {

const uuid = await this.getFileUuid(flags['id'], nonInteractive);

await TrashService.instance.trashItems({ items: [{ uuid, type: 'file' }] });
await TrashService.instance.trashItems({ items: [{ uuid, type: 'file', id: null }] });
const message = 'File trashed successfully.';
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, file: { uuid } };
Expand Down
2 changes: 1 addition & 1 deletion src/commands/trash-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class TrashFolder extends Command {

const uuid = await this.getFolderUuid(flags['id'], nonInteractive);

await TrashService.instance.trashItems({ items: [{ uuid, type: 'folder' }] });
await TrashService.instance.trashItems({ items: [{ uuid, type: 'folder', id: null }] });
const message = 'Folder trashed successfully.';
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, folder: { uuid } };
Expand Down
2 changes: 1 addition & 1 deletion src/commands/trash-restore-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default class TrashRestoreFile extends Command {
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const file = await DriveFileService.instance.moveFile({ fileUuid, destinationFolderUuid });
const file = await DriveFileService.instance.moveFile(fileUuid, { destinationFolder: destinationFolderUuid });
const message = `File restored successfully to: ${destinationFolderUuid}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, file };
Expand Down
4 changes: 3 additions & 1 deletion src/commands/trash-restore-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export default class TrashRestoreFolder extends Command {
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const folder = await DriveFolderService.instance.moveFolder({ folderUuid, destinationFolderUuid });
const folder = await DriveFolderService.instance.moveFolder(folderUuid, {
destinationFolder: destinationFolderUuid,
});
const message = `Folder restored successfully to: ${destinationFolderUuid}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, folder };
Expand Down
28 changes: 17 additions & 11 deletions src/commands/upload-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DriveFileService } from '../services/drive/drive-file.service';
import { CryptoService } from '../services/crypto.service';
import { DownloadService } from '../services/network/download.service';
import { ErrorUtils } from '../utils/errors.utils';
import { MissingCredentialsError, NotValidDirectoryError, NotValidFolderUuidError } from '../types/command.types';
import { NotValidDirectoryError, NotValidFolderUuidError } from '../types/command.types';
import { ValidationService } from '../services/validation.service';
import { EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types';
import { ThumbnailService } from '../services/thumbnail.service';
Expand Down Expand Up @@ -46,8 +46,7 @@ export default class UploadFile extends Command {

const nonInteractive = flags['non-interactive'];

const userCredentials = await ConfigService.instance.readUser();
if (!userCredentials) throw new MissingCredentialsError();
const { user } = await AuthService.instance.getAuthDetails();

const filePath = await this.getFilePath(flags['file'], nonInteractive);

Expand All @@ -62,12 +61,11 @@ export default class UploadFile extends Command {
let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);
if (destinationFolderUuid.trim().length === 0) {
// destinationFolderUuid is empty from flags&prompt, which means we should use RootFolderUuid
destinationFolderUuid = userCredentials.user.rootFolderId;
destinationFolderUuid = user.rootFolderId;
}

// 1. Prepare the network
CLIUtils.doing('Preparing Network', flags['json']);
const { user } = await AuthService.instance.getAuthDetails();
const networkModule = SdkManager.instance.getNetwork({
user: user.bridgeUser,
pass: user.userId,
Expand Down Expand Up @@ -133,14 +131,15 @@ export default class UploadFile extends Command {

// 3. Create the file in Drive
const createdDriveFile = await DriveFileService.instance.createFile({
plain_name: fileInfo.name,
plainName: fileInfo.name,
type: fileType,
size: stats.size,
folder_id: destinationFolderUuid,
id: fileId,
folderUuid: destinationFolderUuid,
fileId: fileId,
bucket: user.bucket,
encrypt_version: EncryptionVersion.Aes03,
name: '',
encryptVersion: EncryptionVersion.Aes03,
creationTime: stats.birthtime?.toISOString(),
modificationTime: stats.mtime?.toISOString(),
});

try {
Expand Down Expand Up @@ -169,7 +168,14 @@ export default class UploadFile extends Command {
// eslint-disable-next-line max-len
const message = `File uploaded in ${uploadTime}ms, view it at ${ConfigService.instance.get('DRIVE_WEB_URL')}/file/${createdDriveFile.uuid}`;
CLIUtils.success(this.log.bind(this), message);
return { success: true, message, file: createdDriveFile };
return {
success: true,
message,
file: {
...createdDriveFile,
plainName: fileInfo.name,
},
};
};

public catch = async (error: Error) => {
Expand Down
10 changes: 4 additions & 6 deletions src/commands/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ export default class Whoami extends Command {
};

private checkUserAndTokens = (loginCreds: LoginCredentials): boolean => {
if (!(loginCreds?.newToken && loginCreds?.token && loginCreds?.user?.mnemonic)) {
if (!(loginCreds?.token && loginCreds?.user?.mnemonic)) {
return false;
}
const oldTokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.token);
const newTokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.newToken);
const tokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.token);
const goodMnemonic = ValidationService.instance.validateMnemonic(loginCreds.user.mnemonic);
const goodToken = oldTokenDetails.isValid && !oldTokenDetails.expiration.expired;
const goodNewToken = newTokenDetails.isValid && !newTokenDetails.expiration.expired;
const goodToken = tokenDetails.isValid && !tokenDetails.expiration.expired;

return goodToken && goodNewToken && goodMnemonic;
return goodToken && goodMnemonic;
};
}
7 changes: 2 additions & 5 deletions src/hooks/prerun/auth_check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ const hook: Hook<'prerun'> = async function (opts) {
if (!CommandsToSkip.map((command) => command.name).includes(Command.name)) {
CLIUtils.doing('Checking credentials', jsonFlag);
try {
const { token, newToken } = await AuthService.instance.getAuthDetails();
SdkManager.init({
token,
newToken,
});
const { token } = await AuthService.instance.getAuthDetails();
SdkManager.init({ token });
CLIUtils.done(jsonFlag);
CLIUtils.clearPreviousLine(jsonFlag);
} catch (error) {
Expand Down
72 changes: 43 additions & 29 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class AuthService {
};

const data = await authClient.login(loginDetails, CryptoService.cryptoProvider);
const { user, token, newToken } = data;
const { user, newToken } = data;
const { privateKey, publicKey } = user;

const plainPrivateKeyInBase64 = privateKey
Expand All @@ -54,8 +54,7 @@ export class AuthService {
};
return {
user: clearUser,
token: token,
newToken: newToken,
token: newToken,
lastLoggedInAt: new Date().toISOString(),
lastTokenRefreshAt: new Date().toISOString(),
};
Expand Down Expand Up @@ -83,62 +82,77 @@ export class AuthService {
*/
public getAuthDetails = async (): Promise<LoginCredentials> => {
let loginCreds = await ConfigService.instance.readUser();
if (!loginCreds?.newToken || !loginCreds?.token || !loginCreds?.user?.mnemonic) {
if (!loginCreds?.token || !loginCreds?.user?.mnemonic) {
throw new MissingCredentialsError();
}

const oldTokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.token);
const newTokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.newToken);
const tokenDetails = ValidationService.instance.validateTokenAndCheckExpiration(loginCreds.token);
const isValidMnemonic = ValidationService.instance.validateMnemonic(loginCreds.user.mnemonic);
if (!oldTokenDetails.isValid || !newTokenDetails.isValid || !isValidMnemonic) {

if (!tokenDetails.isValid || !isValidMnemonic) {
throw new InvalidCredentialsError();
}

if (oldTokenDetails.expiration.expired || newTokenDetails.expiration.expired) {
if (tokenDetails.expiration.expired) {
throw new ExpiredCredentialsError();
}

const refreshOldToken = !oldTokenDetails.expiration.expired && oldTokenDetails.expiration.refreshRequired;
const refreshNewToken = !newTokenDetails.expiration.expired && newTokenDetails.expiration.refreshRequired;

if (refreshOldToken || refreshNewToken) {
loginCreds = await this.refreshUserTokens(loginCreds);
const refreshToken = tokenDetails.expiration.refreshRequired;
if (refreshToken) {
loginCreds = await this.refreshUserToken(loginCreds);
}

return loginCreds;
};

/**
* Refreshes the user auth details
*
* @returns The user details and its auth tokens
* @returns The user details and its renewed auth token
*/
public refreshUserTokens = async (oldCreds: LoginCredentials): Promise<LoginCredentials> => {
SdkManager.init({
token: oldCreds.token,
newToken: oldCreds.newToken,
});
public refreshUserDetails = async (oldCreds: LoginCredentials): Promise<LoginCredentials> => {
SdkManager.init({ token: oldCreds.token });
const usersClient = SdkManager.instance.getUsers();
const newCreds = await usersClient.getUserData({ userUuid: oldCreds.user.uuid });

const loginCreds = {
const loginCreds: LoginCredentials = {
user: {
...newCreds.user,
mnemonic: oldCreds.user.mnemonic,
privateKey: oldCreds.user.privateKey,
},
token: newCreds.oldToken,
newToken: newCreds.newToken,
token: newCreds.newToken,
lastLoggedInAt: oldCreds.lastLoggedInAt,
lastTokenRefreshAt: new Date().toISOString(),
};
SdkManager.init({
token: newCreds.oldToken,
newToken: newCreds.newToken,
});
SdkManager.init({ token: newCreds.newToken });
await ConfigService.instance.saveUser(loginCreds);
return loginCreds;
};

/**
* Refreshes the user tokens
*
* @returns The user details and its renewed auth token
*/
public refreshUserToken = async (oldCreds: LoginCredentials): Promise<LoginCredentials> => {
SdkManager.init({ token: oldCreds.token });

const usersClient = SdkManager.instance.getUsers();
const newCreds = await usersClient.refreshUser();

SdkManager.init({ token: newCreds.newToken });

const newLoginCreds: LoginCredentials = {
...oldCreds,
token: newCreds.newToken,
lastLoggedInAt: oldCreds.lastLoggedInAt,
lastTokenRefreshAt: new Date().toISOString(),
};

await ConfigService.instance.saveUser(newLoginCreds);
return newLoginCreds;
};

/**
* Logs the user out of the application by invoking the logout method
* from the authentication client. This will terminate the user's session
Expand All @@ -149,11 +163,11 @@ export class AuthService {
public logout = async (): Promise<void> => {
try {
const user = await ConfigService.instance.readUser();
if (!user || !user.newToken) {
if (!user || !user.token) {
return;
}
const authClient = SdkManager.instance.getAuth();
return authClient.logout(user.newToken);
return authClient.logout(user.token);
} catch {
//no op
}
Expand Down
Loading
Loading