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
10 changes: 7 additions & 3 deletions .github/workflows/publish-docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,25 @@ jobs:
echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV
echo "Docker tag will be: $DOCKER_TAG"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: |
internxt/webdav:latest
internxt/webdav:${{ env.DOCKER_TAG }}
232 changes: 132 additions & 100 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ You can also run the `internxt/webdav` image directly on popular NAS devices lik
1. Open Container Station.
2. Click **Create Container** and search for `internxt/webdav`.
3. Select the latest image and click **Next**.
4. Set environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings.
4. Set environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings (e.g., `3005:3005`).
5. Apply settings and start the container.


Expand Down
4 changes: 2 additions & 2 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ if [ -z "$INXT_USER" ] || [ -z "$INXT_PASSWORD" ]; then
fi


echo "Logging into your account [$INXT_USER]"
echo "Logging into your account [$INXT_USER] using legacy authentication..."

LOGIN_CMD="internxt login -x -e=\"$INXT_USER\" -p=\"$INXT_PASSWORD\""
LOGIN_CMD="internxt login-legacy -x -e=\"$INXT_USER\" -p=\"$INXT_PASSWORD\""

if [ -n "$INXT_OTPTOKEN" ]; then
echo "Using 2FA secret token"
Expand Down
43 changes: 22 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"author": "Internxt <hello@internxt.com>",
"version": "1.5.8",
"version": "1.6.0",
"description": "Internxt CLI to manage your encrypted storage",
"scripts": {
"build": "yarn clean && tsc",
"clean": "rimraf dist coverage tsconfig.tsbuildinfo oclif.manifest.json",
"lint": "eslint .",
"pretty": "prettier --write **/*.{js,jsx,tsx,ts}",
"format": "prettier --write **/*.{js,jsx,tsx,ts}",
"postpack": "rimraf oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
Expand Down Expand Up @@ -36,51 +36,52 @@
"/oclif.manifest.json"
],
"dependencies": {
"@inquirer/prompts": "7.9.0",
"@inquirer/prompts": "7.10.1",
"@internxt/inxt-js": "2.2.9",
"@internxt/lib": "1.3.1",
"@internxt/sdk": "1.11.12",
"@oclif/core": "4.7.2",
"@oclif/plugin-autocomplete": "3.2.36",
"axios": "1.12.2",
"@internxt/lib": "1.4.1",
"@internxt/sdk": "1.11.15",
"@oclif/core": "4.8.0",
"@oclif/plugin-autocomplete": "3.2.39",
"axios": "1.13.2",
"bip39": "3.1.0",
"body-parser": "2.2.0",
"cli-progress": "3.12.0",
"dayjs": "1.11.18",
"dayjs": "1.11.19",
"dotenv": "17.2.3",
"express": "5.1.0",
"express-async-handler": "1.2.0",
"fast-xml-parser": "5.3.0",
"fast-xml-parser": "5.3.2",
"mime-types": "3.0.1",
"open": "11.0.0",
"openpgp": "6.2.2",
"otpauth": "9.4.1",
"pm2": "6.0.13",
"range-parser": "1.2.1",
"selfsigned": "3.0.1",
"tty-table": "4.2.3",
"selfsigned": "4.0.0",
"tty-table": "5.0.0",
"winston": "3.18.3"
},
"devDependencies": {
"@internxt/eslint-config-internxt": "2.0.1",
"@internxt/prettier-config": "internxt/prettier-config#v1.0.2",
"@openpgp/web-stream-tools": "0.1.3",
"@types/cli-progress": "3.11.6",
"@types/express": "5.0.3",
"@types/express": "5.0.5",
"@types/mime-types": "3.0.1",
"@types/node": "22.18.12",
"@types/range-parser": "1.2.7",
"@vitest/coverage-istanbul": "3.2.4",
"@vitest/spy": "3.2.4",
"eslint": "9.38.0",
"@vitest/coverage-istanbul": "4.0.10",
"@vitest/spy": "4.0.10",
"eslint": "9.39.1",
"husky": "9.1.7",
"lint-staged": "16.2.4",
"nodemon": "3.1.10",
"oclif": "4.22.32",
"lint-staged": "16.2.6",
"nodemon": "3.1.11",
"oclif": "4.22.47",
"prettier": "3.6.2",
"rimraf": "6.0.1",
"rimraf": "6.1.0",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "3.2.4",
"vitest": "4.0.10",
"vitest-mock-express": "2.2.0"
},
"optionalDependencies": {
Expand Down
166 changes: 166 additions & 0 deletions src/commands/login-legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Command, Flags } from '@oclif/core';
import { EmptyPasswordError, NotValidEmailError, NotValidTwoFactorCodeError } from '../types/command.types';
import { AuthService } from '../services/auth.service';
import { ConfigService } from '../services/config.service';
import { ValidationService } from '../services/validation.service';
import { CLIUtils } from '../utils/cli.utils';
import { SdkManager } from '../services/sdk-manager.service';
import * as OTPAuth from 'otpauth';

export default class LoginLegacy extends Command {
static readonly args = {};
static readonly description =
'[Legacy] Logs into an Internxt account using user and password. ' +
'If the account is two-factor protected, then an extra code will be required.';
static readonly aliases = [];
static readonly examples = ['<%= config.bin %> <%= command.id %>'];
static readonly flags = {
...CLIUtils.CommonFlags,
email: Flags.string({
char: 'e',
aliases: ['mail'],
env: 'INXT_USER',
description: 'The email to log in',
required: false,
}),
password: Flags.string({
char: 'p',
aliases: ['pass'],
env: 'INXT_PASSWORD',
description: 'The plain password to log in',
required: false,
}),
twofactor: Flags.string({
char: 'w',
aliases: ['two', 'two-factor'],
env: 'INXT_TWOFACTORCODE',
description: 'The two factor auth code (TOTP). ',
required: false,
helpValue: '123456',
}),
twofactortoken: Flags.string({
char: 't',
aliases: ['otp', 'otp-token'],
env: 'INXT_OTPTOKEN',
description:
'The TOTP secret token. It is used to generate a TOTP code if needed.' +
' It has prority over the two factor code flag.',
required: false,
helpValue: 'token',
}),
};
static readonly enableJsonFlag = true;

public run = async () => {
const { flags } = await this.parse(LoginLegacy);

const nonInteractive = flags['non-interactive'];
const email = await this.getEmail(flags['email'], nonInteractive);
const password = await this.getPassword(flags['password'], nonInteractive);

const is2FANeeded = await AuthService.instance.is2FANeeded(email);
let twoFactorCode: string | undefined;
if (is2FANeeded) {
const twoFactorToken = flags['twofactortoken'];
if (twoFactorToken && twoFactorToken.trim().length > 0) {
const totp = new OTPAuth.TOTP({
secret: twoFactorToken,
digits: 6,
});
twoFactorCode = totp.generate();
} else {
twoFactorCode = await this.getTwoFactorCode(flags['twofactor'], nonInteractive);
}
}

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

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

await ConfigService.instance.saveUser(loginCredentials);
const message = `Succesfully logged in to: ${loginCredentials.user.email}`;
CLIUtils.success(this.log.bind(this), message);
return {
success: true,
message,
login: loginCredentials,
};
};

public catch = async (error: Error) => {
const { flags } = await this.parse(LoginLegacy);
CLIUtils.catchError({
error,
command: this.id,
logReporter: this.log.bind(this),
jsonFlag: flags['json'],
});
this.exit(1);
};

private getEmail = async (emailFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
const email = await CLIUtils.getValueFromFlag(
{
value: emailFlag,
name: LoginLegacy.flags['email'].name,
},
{
nonInteractive,
prompt: {
message: 'What is your email?',
options: { type: 'input' },
},
},
{
validate: ValidationService.instance.validateEmail,
error: new NotValidEmailError(),
},
this.log.bind(this),
);
return email;
};

private getPassword = async (passwordFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
const password = await CLIUtils.getValueFromFlag(
{
value: passwordFlag,
name: LoginLegacy.flags['password'].name,
},
{
nonInteractive,
prompt: {
message: 'What is your password?',
options: { type: 'password' },
},
},
{
validate: ValidationService.instance.validateStringIsNotEmpty,
error: new EmptyPasswordError(),
},
this.log.bind(this),
);
return password;
};

private getTwoFactorCode = async (twoFactorFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
const twoFactor = await CLIUtils.getValueFromFlag(
{
value: twoFactorFlag,
name: LoginLegacy.flags['twofactor'].name,
},
{
nonInteractive,
prompt: {
message: 'What is your two-factor code?',
options: { type: 'mask' },
},
},
{
validate: ValidationService.instance.validate2FA,
error: new NotValidTwoFactorCodeError(),
},
this.log.bind(this),
);
return twoFactor;
};
}
Loading
Loading