From 56f47830758ff8e92abcfcc5f35d474ab12fe215 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Oct 2022 15:37:15 +0200 Subject: Support two factor authentication in backend --- server/tests/api/check-params/index.ts | 5 +- server/tests/api/check-params/two-factor.ts | 275 ++++++++++++++++++++++++++++ server/tests/api/users/index.ts | 1 + server/tests/api/users/two-factor.ts | 153 ++++++++++++++++ 4 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 server/tests/api/check-params/two-factor.ts create mode 100644 server/tests/api/users/two-factor.ts (limited to 'server/tests/api') diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index cd7a38459..33dc8fb76 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts @@ -2,6 +2,7 @@ import './abuses' import './accounts' import './blocklist' import './bulk' +import './channel-import-videos' import './config' import './contact-form' import './custom-pages' @@ -17,6 +18,7 @@ import './redundancy' import './search' import './services' import './transcoding' +import './two-factor' import './upload-quota' import './user-notifications' import './user-subscriptions' @@ -24,12 +26,11 @@ import './users-admin' import './users' import './video-blacklist' import './video-captions' +import './video-channel-syncs' import './video-channels' import './video-comments' import './video-files' import './video-imports' -import './video-channel-syncs' -import './channel-import-videos' import './video-playlists' import './video-source' import './video-studio' diff --git a/server/tests/api/check-params/two-factor.ts b/server/tests/api/check-params/two-factor.ts new file mode 100644 index 000000000..e7ca5490c --- /dev/null +++ b/server/tests/api/check-params/two-factor.ts @@ -0,0 +1,275 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { HttpStatusCode } from '@shared/models' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, TwoFactorCommand } from '@shared/server-commands' + +describe('Test two factor API validators', function () { + let server: PeerTubeServer + + let rootId: number + let rootPassword: string + let rootRequestToken: string + let rootOTPToken: string + + let userId: number + let userToken = '' + let userPassword: string + let userRequestToken: string + let userOTPToken: string + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(30000) + + { + server = await createSingleServer(1) + await setAccessTokensToServers([ server ]) + } + + { + const result = await server.users.generate('user1') + userToken = result.token + userId = result.userId + userPassword = result.password + } + + { + const { id } = await server.users.getMyInfo() + rootId = id + rootPassword = server.store.user.password + } + }) + + describe('When requesting two factor', function () { + + it('Should fail with an unknown user id', async function () { + await server.twoFactor.request({ userId: 42, currentPassword: rootPassword, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + }) + + it('Should fail with an invalid user id', async function () { + await server.twoFactor.request({ + userId: 'invalid' as any, + currentPassword: rootPassword, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail to request another user two factor without the appropriate rights', async function () { + await server.twoFactor.request({ + userId: rootId, + token: userToken, + currentPassword: userPassword, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed to request another user two factor with the appropriate rights', async function () { + await server.twoFactor.request({ userId, currentPassword: rootPassword }) + }) + + it('Should fail to request two factor without a password', async function () { + await server.twoFactor.request({ + userId, + token: userToken, + currentPassword: undefined, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail to request two factor with an incorrect password', async function () { + await server.twoFactor.request({ + userId, + token: userToken, + currentPassword: rootPassword, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed to request my two factor auth', async function () { + { + const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword }) + userRequestToken = otpRequest.requestToken + userOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate() + } + + { + const { otpRequest } = await server.twoFactor.request({ userId: rootId, currentPassword: rootPassword }) + rootRequestToken = otpRequest.requestToken + rootOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate() + } + }) + }) + + describe('When confirming two factor request', function () { + + it('Should fail with an unknown user id', async function () { + await server.twoFactor.confirmRequest({ + userId: 42, + requestToken: rootRequestToken, + otpToken: rootOTPToken, + expectedStatus: HttpStatusCode.NOT_FOUND_404 + }) + }) + + it('Should fail with an invalid user id', async function () { + await server.twoFactor.confirmRequest({ + userId: 'invalid' as any, + requestToken: rootRequestToken, + otpToken: rootOTPToken, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail to confirm another user two factor request without the appropriate rights', async function () { + await server.twoFactor.confirmRequest({ + userId: rootId, + token: userToken, + requestToken: rootRequestToken, + otpToken: rootOTPToken, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should fail without request token', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: undefined, + otpToken: userOTPToken, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail with an invalid request token', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: 'toto', + otpToken: userOTPToken, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should fail with request token of another user', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: rootRequestToken, + otpToken: userOTPToken, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should fail without an otp token', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: userRequestToken, + otpToken: undefined, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail with a bad otp token', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: userRequestToken, + otpToken: '123456', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed to confirm another user two factor request with the appropriate rights', async function () { + await server.twoFactor.confirmRequest({ + userId, + requestToken: userRequestToken, + otpToken: userOTPToken + }) + + // Reinit + await server.twoFactor.disable({ userId, currentPassword: rootPassword }) + }) + + it('Should succeed to confirm my two factor request', async function () { + await server.twoFactor.confirmRequest({ + userId, + token: userToken, + requestToken: userRequestToken, + otpToken: userOTPToken + }) + }) + + it('Should fail to confirm again two factor request', async function () { + await server.twoFactor.confirmRequest({ + userId, + token: userToken, + requestToken: userRequestToken, + otpToken: userOTPToken, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + }) + + describe('When disabling two factor', function () { + + it('Should fail with an unknown user id', async function () { + await server.twoFactor.disable({ + userId: 42, + currentPassword: rootPassword, + expectedStatus: HttpStatusCode.NOT_FOUND_404 + }) + }) + + it('Should fail with an invalid user id', async function () { + await server.twoFactor.disable({ + userId: 'invalid' as any, + currentPassword: rootPassword, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should fail to disable another user two factor without the appropriate rights', async function () { + await server.twoFactor.disable({ + userId: rootId, + token: userToken, + currentPassword: userPassword, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should fail to disabled two factor with an incorrect password', async function () { + await server.twoFactor.disable({ + userId, + token: userToken, + currentPassword: rootPassword, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed to disable another user two factor with the appropriate rights', async function () { + await server.twoFactor.disable({ userId, currentPassword: rootPassword }) + + // Reinit + const { otpRequest } = await server.twoFactor.request({ userId, currentPassword: rootPassword }) + await server.twoFactor.confirmRequest({ + userId, + requestToken: otpRequest.requestToken, + otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate() + }) + }) + + it('Should succeed to update my two factor auth', async function () { + await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword }) + }) + + it('Should fail to disable again two factor', async function () { + await server.twoFactor.disable({ + userId, + token: userToken, + currentPassword: userPassword, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + }) + + after(async function () { + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts index c65152c6f..643f1a531 100644 --- a/server/tests/api/users/index.ts +++ b/server/tests/api/users/index.ts @@ -1,3 +1,4 @@ +import './two-factor' import './user-subscriptions' import './user-videos' import './users' diff --git a/server/tests/api/users/two-factor.ts b/server/tests/api/users/two-factor.ts new file mode 100644 index 000000000..450aac4dc --- /dev/null +++ b/server/tests/api/users/two-factor.ts @@ -0,0 +1,153 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { expect } from 'chai' +import { expectStartWith } from '@server/tests/shared' +import { HttpStatusCode } from '@shared/models' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, TwoFactorCommand } from '@shared/server-commands' + +async function login (options: { + server: PeerTubeServer + password?: string + otpToken?: string + expectedStatus?: HttpStatusCode +}) { + const { server, password = server.store.user.password, otpToken, expectedStatus } = options + + const user = { username: server.store.user.username, password } + const { res, body: { access_token: token } } = await server.login.loginAndGetResponse({ user, otpToken, expectedStatus }) + + return { res, token } +} + +describe('Test users', function () { + let server: PeerTubeServer + let rootId: number + let otpSecret: string + let requestToken: string + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1) + + await setAccessTokensToServers([ server ]) + + const { id } = await server.users.getMyInfo() + rootId = id + }) + + it('Should not add the header on login if two factor is not enabled', async function () { + const { res, token } = await login({ server }) + + expect(res.header['x-peertube-otp']).to.not.exist + + await server.users.getMyInfo({ token }) + }) + + it('Should request two factor and get the secret and uri', async function () { + const { otpRequest } = await server.twoFactor.request({ + userId: rootId, + currentPassword: server.store.user.password + }) + + expect(otpRequest.requestToken).to.exist + + expect(otpRequest.secret).to.exist + expect(otpRequest.secret).to.have.lengthOf(32) + + expect(otpRequest.uri).to.exist + expectStartWith(otpRequest.uri, 'otpauth://') + expect(otpRequest.uri).to.include(otpRequest.secret) + + requestToken = otpRequest.requestToken + otpSecret = otpRequest.secret + }) + + it('Should not have two factor confirmed yet', async function () { + const { twoFactorEnabled } = await server.users.getMyInfo() + expect(twoFactorEnabled).to.be.false + }) + + it('Should confirm two factor', async function () { + await server.twoFactor.confirmRequest({ + userId: rootId, + otpToken: TwoFactorCommand.buildOTP({ secret: otpSecret }).generate(), + requestToken + }) + }) + + it('Should not add the header on login if two factor is enabled and password is incorrect', async function () { + const { res, token } = await login({ server, password: 'fake', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + + expect(res.header['x-peertube-otp']).to.not.exist + expect(token).to.not.exist + }) + + it('Should add the header on login if two factor is enabled and password is correct', async function () { + const { res, token } = await login({ server, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + + expect(res.header['x-peertube-otp']).to.exist + expect(token).to.not.exist + + await server.users.getMyInfo({ token }) + }) + + it('Should not login with correct password and incorrect otp secret', async function () { + const otp = TwoFactorCommand.buildOTP({ secret: 'a'.repeat(32) }) + + const { res, token } = await login({ server, otpToken: otp.generate(), expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + + expect(res.header['x-peertube-otp']).to.not.exist + expect(token).to.not.exist + }) + + it('Should not login with correct password and incorrect otp code', async function () { + const { res, token } = await login({ server, otpToken: '123456', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + + expect(res.header['x-peertube-otp']).to.not.exist + expect(token).to.not.exist + }) + + it('Should not login with incorrect password and correct otp code', async function () { + const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate() + + const { res, token } = await login({ server, password: 'fake', otpToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + + expect(res.header['x-peertube-otp']).to.not.exist + expect(token).to.not.exist + }) + + it('Should correctly login with correct password and otp code', async function () { + const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate() + + const { res, token } = await login({ server, otpToken }) + + expect(res.header['x-peertube-otp']).to.not.exist + expect(token).to.exist + + await server.users.getMyInfo({ token }) + }) + + it('Should have two factor enabled when getting my info', async function () { + const { twoFactorEnabled } = await server.users.getMyInfo() + expect(twoFactorEnabled).to.be.true + }) + + it('Should disable two factor and be able to login without otp token', async function () { + await server.twoFactor.disable({ userId: rootId, currentPassword: server.store.user.password }) + + const { res, token } = await login({ server }) + expect(res.header['x-peertube-otp']).to.not.exist + + await server.users.getMyInfo({ token }) + }) + + it('Should have two factor disabled when getting my info', async function () { + const { twoFactorEnabled } = await server.users.getMyInfo() + expect(twoFactorEnabled).to.be.false + }) + + after(async function () { + await cleanupTests([ server ]) + }) +}) -- cgit v1.2.3 From 2166c058f34dff6f91566930d12448805d829de7 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 7 Oct 2022 14:23:42 +0200 Subject: Allow admins to disable two factor auth --- server/tests/api/check-params/two-factor.ts | 29 ++++++--- server/tests/api/users/two-factor.ts | 95 +++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 32 deletions(-) (limited to 'server/tests/api') diff --git a/server/tests/api/check-params/two-factor.ts b/server/tests/api/check-params/two-factor.ts index e7ca5490c..f8365f1b5 100644 --- a/server/tests/api/check-params/two-factor.ts +++ b/server/tests/api/check-params/two-factor.ts @@ -86,6 +86,15 @@ describe('Test two factor API validators', function () { }) }) + it('Should succeed to request two factor without a password when targeting a remote user with an admin account', async function () { + await server.twoFactor.request({ userId }) + }) + + it('Should fail to request two factor without a password when targeting myself with an admin account', async function () { + await server.twoFactor.request({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.twoFactor.request({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + it('Should succeed to request my two factor auth', async function () { { const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword }) @@ -234,7 +243,7 @@ describe('Test two factor API validators', function () { }) }) - it('Should fail to disabled two factor with an incorrect password', async function () { + it('Should fail to disable two factor with an incorrect password', async function () { await server.twoFactor.disable({ userId, token: userToken, @@ -243,16 +252,20 @@ describe('Test two factor API validators', function () { }) }) + it('Should succeed to disable two factor without a password when targeting a remote user with an admin account', async function () { + await server.twoFactor.disable({ userId }) + await server.twoFactor.requestAndConfirm({ userId }) + }) + + it('Should fail to disable two factor without a password when targeting myself with an admin account', async function () { + await server.twoFactor.disable({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.twoFactor.disable({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + it('Should succeed to disable another user two factor with the appropriate rights', async function () { await server.twoFactor.disable({ userId, currentPassword: rootPassword }) - // Reinit - const { otpRequest } = await server.twoFactor.request({ userId, currentPassword: rootPassword }) - await server.twoFactor.confirmRequest({ - userId, - requestToken: otpRequest.requestToken, - otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate() - }) + await server.twoFactor.requestAndConfirm({ userId }) }) it('Should succeed to update my two factor auth', async function () { diff --git a/server/tests/api/users/two-factor.ts b/server/tests/api/users/two-factor.ts index 450aac4dc..0dcab9e17 100644 --- a/server/tests/api/users/two-factor.ts +++ b/server/tests/api/users/two-factor.ts @@ -7,13 +7,14 @@ import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServ async function login (options: { server: PeerTubeServer - password?: string + username: string + password: string otpToken?: string expectedStatus?: HttpStatusCode }) { - const { server, password = server.store.user.password, otpToken, expectedStatus } = options + const { server, username, password, otpToken, expectedStatus } = options - const user = { username: server.store.user.username, password } + const user = { username, password } const { res, body: { access_token: token } } = await server.login.loginAndGetResponse({ user, otpToken, expectedStatus }) return { res, token } @@ -21,23 +22,28 @@ async function login (options: { describe('Test users', function () { let server: PeerTubeServer - let rootId: number let otpSecret: string let requestToken: string + const userUsername = 'user1' + let userId: number + let userPassword: string + let userToken: string + before(async function () { this.timeout(30000) server = await createSingleServer(1) await setAccessTokensToServers([ server ]) - - const { id } = await server.users.getMyInfo() - rootId = id + const res = await server.users.generate(userUsername) + userId = res.userId + userPassword = res.password + userToken = res.token }) it('Should not add the header on login if two factor is not enabled', async function () { - const { res, token } = await login({ server }) + const { res, token } = await login({ server, username: userUsername, password: userPassword }) expect(res.header['x-peertube-otp']).to.not.exist @@ -45,10 +51,7 @@ describe('Test users', function () { }) it('Should request two factor and get the secret and uri', async function () { - const { otpRequest } = await server.twoFactor.request({ - userId: rootId, - currentPassword: server.store.user.password - }) + const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword }) expect(otpRequest.requestToken).to.exist @@ -64,27 +67,33 @@ describe('Test users', function () { }) it('Should not have two factor confirmed yet', async function () { - const { twoFactorEnabled } = await server.users.getMyInfo() + const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken }) expect(twoFactorEnabled).to.be.false }) it('Should confirm two factor', async function () { await server.twoFactor.confirmRequest({ - userId: rootId, + userId, + token: userToken, otpToken: TwoFactorCommand.buildOTP({ secret: otpSecret }).generate(), requestToken }) }) it('Should not add the header on login if two factor is enabled and password is incorrect', async function () { - const { res, token } = await login({ server, password: 'fake', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + const { res, token } = await login({ server, username: userUsername, password: 'fake', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) expect(res.header['x-peertube-otp']).to.not.exist expect(token).to.not.exist }) it('Should add the header on login if two factor is enabled and password is correct', async function () { - const { res, token } = await login({ server, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + const { res, token } = await login({ + server, + username: userUsername, + password: userPassword, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) expect(res.header['x-peertube-otp']).to.exist expect(token).to.not.exist @@ -95,14 +104,26 @@ describe('Test users', function () { it('Should not login with correct password and incorrect otp secret', async function () { const otp = TwoFactorCommand.buildOTP({ secret: 'a'.repeat(32) }) - const { res, token } = await login({ server, otpToken: otp.generate(), expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + const { res, token } = await login({ + server, + username: userUsername, + password: userPassword, + otpToken: otp.generate(), + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) expect(res.header['x-peertube-otp']).to.not.exist expect(token).to.not.exist }) it('Should not login with correct password and incorrect otp code', async function () { - const { res, token } = await login({ server, otpToken: '123456', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + const { res, token } = await login({ + server, + username: userUsername, + password: userPassword, + otpToken: '123456', + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) expect(res.header['x-peertube-otp']).to.not.exist expect(token).to.not.exist @@ -111,7 +132,13 @@ describe('Test users', function () { it('Should not login with incorrect password and correct otp code', async function () { const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate() - const { res, token } = await login({ server, password: 'fake', otpToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + const { res, token } = await login({ + server, + username: userUsername, + password: 'fake', + otpToken, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) expect(res.header['x-peertube-otp']).to.not.exist expect(token).to.not.exist @@ -120,7 +147,7 @@ describe('Test users', function () { it('Should correctly login with correct password and otp code', async function () { const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate() - const { res, token } = await login({ server, otpToken }) + const { res, token } = await login({ server, username: userUsername, password: userPassword, otpToken }) expect(res.header['x-peertube-otp']).to.not.exist expect(token).to.exist @@ -129,21 +156,41 @@ describe('Test users', function () { }) it('Should have two factor enabled when getting my info', async function () { - const { twoFactorEnabled } = await server.users.getMyInfo() + const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken }) expect(twoFactorEnabled).to.be.true }) it('Should disable two factor and be able to login without otp token', async function () { - await server.twoFactor.disable({ userId: rootId, currentPassword: server.store.user.password }) + await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword }) - const { res, token } = await login({ server }) + const { res, token } = await login({ server, username: userUsername, password: userPassword }) expect(res.header['x-peertube-otp']).to.not.exist await server.users.getMyInfo({ token }) }) it('Should have two factor disabled when getting my info', async function () { - const { twoFactorEnabled } = await server.users.getMyInfo() + const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken }) + expect(twoFactorEnabled).to.be.false + }) + + it('Should enable two factor auth without password from an admin', async function () { + const { otpRequest } = await server.twoFactor.request({ userId }) + + await server.twoFactor.confirmRequest({ + userId, + otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate(), + requestToken: otpRequest.requestToken + }) + + const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken }) + expect(twoFactorEnabled).to.be.true + }) + + it('Should disable two factor auth without password from an admin', async function () { + await server.twoFactor.disable({ userId }) + + const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken }) expect(twoFactorEnabled).to.be.false }) -- cgit v1.2.3