Skip to content

Commit 5a2d324

Browse files
authored
Merge pull request #415 from internxt/feat/pb-5115-add-universal-link-login
[PB-5115] feat:/add-universal-link-login
2 parents 16cc1a4 + 7dd2590 commit 5a2d324

23 files changed

Lines changed: 1910 additions & 1426 deletions

.github/workflows/publish-docker-image.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,25 @@ jobs:
3737
echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV
3838
echo "Docker tag will be: $DOCKER_TAG"
3939
40-
- name: Set up Docker Buildx
41-
uses: docker/setup-buildx-action@v3
42-
4340
- name: Log in to Docker Hub
4441
uses: docker/login-action@v3
4542
with:
4643
username: ${{ secrets.DOCKERHUB_USERNAME }}
4744
password: ${{ secrets.DOCKERHUB_TOKEN }}
4845

46+
- name: Set up QEMU
47+
uses: docker/setup-qemu-action@v3
48+
49+
- name: Set up Docker Buildx
50+
uses: docker/setup-buildx-action@v3
51+
4952
- name: Build and push Docker image
5053
uses: docker/build-push-action@v6
5154
with:
5255
context: .
5356
file: ./Dockerfile
5457
push: true
58+
platforms: linux/amd64,linux/arm64
5559
tags: |
5660
internxt/webdav:latest
5761
internxt/webdav:${{ env.DOCKER_TAG }}

README.md

Lines changed: 132 additions & 100 deletions
Large diffs are not rendered by default.

docker/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ You can also run the `internxt/webdav` image directly on popular NAS devices lik
6565
1. Open Container Station.
6666
2. Click **Create Container** and search for `internxt/webdav`.
6767
3. Select the latest image and click **Next**.
68-
4. Set environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings.
68+
4. Set environment variables (`INXT_USER`, `INXT_PASSWORD`, etc.) and port mappings (e.g., `3005:3005`).
6969
5. Apply settings and start the container.
7070

7171

docker/entrypoint.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ if [ -z "$INXT_USER" ] || [ -z "$INXT_PASSWORD" ]; then
88
fi
99

1010

11-
echo "Logging into your account [$INXT_USER]"
11+
echo "Logging into your account [$INXT_USER] using legacy authentication..."
1212

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

1515
if [ -n "$INXT_OTPTOKEN" ]; then
1616
echo "Using 2FA secret token"

package.json

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"author": "Internxt <hello@internxt.com>",
3-
"version": "1.5.8",
3+
"version": "1.6.0",
44
"description": "Internxt CLI to manage your encrypted storage",
55
"scripts": {
66
"build": "yarn clean && tsc",
77
"clean": "rimraf dist coverage tsconfig.tsbuildinfo oclif.manifest.json",
88
"lint": "eslint .",
9-
"pretty": "prettier --write **/*.{js,jsx,tsx,ts}",
9+
"format": "prettier --write **/*.{js,jsx,tsx,ts}",
1010
"postpack": "rimraf oclif.manifest.json",
1111
"posttest": "yarn lint",
1212
"prepack": "yarn build && oclif manifest && oclif readme",
@@ -36,51 +36,52 @@
3636
"/oclif.manifest.json"
3737
],
3838
"dependencies": {
39-
"@inquirer/prompts": "7.9.0",
39+
"@inquirer/prompts": "7.10.1",
4040
"@internxt/inxt-js": "2.2.9",
41-
"@internxt/lib": "1.3.1",
42-
"@internxt/sdk": "1.11.12",
43-
"@oclif/core": "4.7.2",
44-
"@oclif/plugin-autocomplete": "3.2.36",
45-
"axios": "1.12.2",
41+
"@internxt/lib": "1.4.1",
42+
"@internxt/sdk": "1.11.15",
43+
"@oclif/core": "4.8.0",
44+
"@oclif/plugin-autocomplete": "3.2.39",
45+
"axios": "1.13.2",
4646
"bip39": "3.1.0",
4747
"body-parser": "2.2.0",
4848
"cli-progress": "3.12.0",
49-
"dayjs": "1.11.18",
49+
"dayjs": "1.11.19",
5050
"dotenv": "17.2.3",
5151
"express": "5.1.0",
5252
"express-async-handler": "1.2.0",
53-
"fast-xml-parser": "5.3.0",
53+
"fast-xml-parser": "5.3.2",
5454
"mime-types": "3.0.1",
55+
"open": "11.0.0",
5556
"openpgp": "6.2.2",
5657
"otpauth": "9.4.1",
5758
"pm2": "6.0.13",
5859
"range-parser": "1.2.1",
59-
"selfsigned": "3.0.1",
60-
"tty-table": "4.2.3",
60+
"selfsigned": "4.0.0",
61+
"tty-table": "5.0.0",
6162
"winston": "3.18.3"
6263
},
6364
"devDependencies": {
6465
"@internxt/eslint-config-internxt": "2.0.1",
6566
"@internxt/prettier-config": "internxt/prettier-config#v1.0.2",
6667
"@openpgp/web-stream-tools": "0.1.3",
6768
"@types/cli-progress": "3.11.6",
68-
"@types/express": "5.0.3",
69+
"@types/express": "5.0.5",
6970
"@types/mime-types": "3.0.1",
7071
"@types/node": "22.18.12",
7172
"@types/range-parser": "1.2.7",
72-
"@vitest/coverage-istanbul": "3.2.4",
73-
"@vitest/spy": "3.2.4",
74-
"eslint": "9.38.0",
73+
"@vitest/coverage-istanbul": "4.0.10",
74+
"@vitest/spy": "4.0.10",
75+
"eslint": "9.39.1",
7576
"husky": "9.1.7",
76-
"lint-staged": "16.2.4",
77-
"nodemon": "3.1.10",
78-
"oclif": "4.22.32",
77+
"lint-staged": "16.2.6",
78+
"nodemon": "3.1.11",
79+
"oclif": "4.22.47",
7980
"prettier": "3.6.2",
80-
"rimraf": "6.0.1",
81+
"rimraf": "6.1.0",
8182
"ts-node": "10.9.2",
8283
"typescript": "5.9.3",
83-
"vitest": "3.2.4",
84+
"vitest": "4.0.10",
8485
"vitest-mock-express": "2.2.0"
8586
},
8687
"optionalDependencies": {

src/commands/login-legacy.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { Command, Flags } from '@oclif/core';
2+
import { EmptyPasswordError, NotValidEmailError, NotValidTwoFactorCodeError } from '../types/command.types';
3+
import { AuthService } from '../services/auth.service';
4+
import { ConfigService } from '../services/config.service';
5+
import { ValidationService } from '../services/validation.service';
6+
import { CLIUtils } from '../utils/cli.utils';
7+
import { SdkManager } from '../services/sdk-manager.service';
8+
import * as OTPAuth from 'otpauth';
9+
10+
export default class LoginLegacy extends Command {
11+
static readonly args = {};
12+
static readonly description =
13+
'[Legacy] Logs into an Internxt account using user and password. ' +
14+
'If the account is two-factor protected, then an extra code will be required.';
15+
static readonly aliases = [];
16+
static readonly examples = ['<%= config.bin %> <%= command.id %>'];
17+
static readonly flags = {
18+
...CLIUtils.CommonFlags,
19+
email: Flags.string({
20+
char: 'e',
21+
aliases: ['mail'],
22+
env: 'INXT_USER',
23+
description: 'The email to log in',
24+
required: false,
25+
}),
26+
password: Flags.string({
27+
char: 'p',
28+
aliases: ['pass'],
29+
env: 'INXT_PASSWORD',
30+
description: 'The plain password to log in',
31+
required: false,
32+
}),
33+
twofactor: Flags.string({
34+
char: 'w',
35+
aliases: ['two', 'two-factor'],
36+
env: 'INXT_TWOFACTORCODE',
37+
description: 'The two factor auth code (TOTP). ',
38+
required: false,
39+
helpValue: '123456',
40+
}),
41+
twofactortoken: Flags.string({
42+
char: 't',
43+
aliases: ['otp', 'otp-token'],
44+
env: 'INXT_OTPTOKEN',
45+
description:
46+
'The TOTP secret token. It is used to generate a TOTP code if needed.' +
47+
' It has prority over the two factor code flag.',
48+
required: false,
49+
helpValue: 'token',
50+
}),
51+
};
52+
static readonly enableJsonFlag = true;
53+
54+
public run = async () => {
55+
const { flags } = await this.parse(LoginLegacy);
56+
57+
const nonInteractive = flags['non-interactive'];
58+
const email = await this.getEmail(flags['email'], nonInteractive);
59+
const password = await this.getPassword(flags['password'], nonInteractive);
60+
61+
const is2FANeeded = await AuthService.instance.is2FANeeded(email);
62+
let twoFactorCode: string | undefined;
63+
if (is2FANeeded) {
64+
const twoFactorToken = flags['twofactortoken'];
65+
if (twoFactorToken && twoFactorToken.trim().length > 0) {
66+
const totp = new OTPAuth.TOTP({
67+
secret: twoFactorToken,
68+
digits: 6,
69+
});
70+
twoFactorCode = totp.generate();
71+
} else {
72+
twoFactorCode = await this.getTwoFactorCode(flags['twofactor'], nonInteractive);
73+
}
74+
}
75+
76+
const loginCredentials = await AuthService.instance.doLogin(email, password, twoFactorCode);
77+
78+
SdkManager.init({ token: loginCredentials.token });
79+
80+
await ConfigService.instance.saveUser(loginCredentials);
81+
const message = `Succesfully logged in to: ${loginCredentials.user.email}`;
82+
CLIUtils.success(this.log.bind(this), message);
83+
return {
84+
success: true,
85+
message,
86+
login: loginCredentials,
87+
};
88+
};
89+
90+
public catch = async (error: Error) => {
91+
const { flags } = await this.parse(LoginLegacy);
92+
CLIUtils.catchError({
93+
error,
94+
command: this.id,
95+
logReporter: this.log.bind(this),
96+
jsonFlag: flags['json'],
97+
});
98+
this.exit(1);
99+
};
100+
101+
private getEmail = async (emailFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
102+
const email = await CLIUtils.getValueFromFlag(
103+
{
104+
value: emailFlag,
105+
name: LoginLegacy.flags['email'].name,
106+
},
107+
{
108+
nonInteractive,
109+
prompt: {
110+
message: 'What is your email?',
111+
options: { type: 'input' },
112+
},
113+
},
114+
{
115+
validate: ValidationService.instance.validateEmail,
116+
error: new NotValidEmailError(),
117+
},
118+
this.log.bind(this),
119+
);
120+
return email;
121+
};
122+
123+
private getPassword = async (passwordFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
124+
const password = await CLIUtils.getValueFromFlag(
125+
{
126+
value: passwordFlag,
127+
name: LoginLegacy.flags['password'].name,
128+
},
129+
{
130+
nonInteractive,
131+
prompt: {
132+
message: 'What is your password?',
133+
options: { type: 'password' },
134+
},
135+
},
136+
{
137+
validate: ValidationService.instance.validateStringIsNotEmpty,
138+
error: new EmptyPasswordError(),
139+
},
140+
this.log.bind(this),
141+
);
142+
return password;
143+
};
144+
145+
private getTwoFactorCode = async (twoFactorFlag: string | undefined, nonInteractive: boolean): Promise<string> => {
146+
const twoFactor = await CLIUtils.getValueFromFlag(
147+
{
148+
value: twoFactorFlag,
149+
name: LoginLegacy.flags['twofactor'].name,
150+
},
151+
{
152+
nonInteractive,
153+
prompt: {
154+
message: 'What is your two-factor code?',
155+
options: { type: 'mask' },
156+
},
157+
},
158+
{
159+
validate: ValidationService.instance.validate2FA,
160+
error: new NotValidTwoFactorCodeError(),
161+
},
162+
this.log.bind(this),
163+
);
164+
return twoFactor;
165+
};
166+
}

0 commit comments

Comments
 (0)