diff --git a/lerna.json b/lerna.json index e52513f23c..fde56290f6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.40-alpha.1", + "version": "2.9.40-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index 0c68798164..8ef34196ed 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -22,6 +22,8 @@ export enum Header { TENANT_ID = "x-budibase-tenant-id", VERIFICATION_CODE = "x-budibase-verification-code", RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code", + RESET_PASSWORD_CODE = "x-budibase-reset-password-code", + RETURN_RESET_PASSWORD_CODE = "x-budibase-return-reset-password-code", TOKEN = "x-budibase-token", CSRF_TOKEN = "x-csrf-token", CORRELATION_ID = "x-budibase-correlation-id", diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 8bdf9cbe65..9241289e86 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -102,7 +102,7 @@ class Orchestrator { } cleanupTriggerOutputs(stepId: string, triggerOutput: TriggerOutput) { - if (stepId === CRON_STEP_ID) { + if (stepId === CRON_STEP_ID && !triggerOutput.timestamp) { triggerOutput.timestamp = Date.now() } return triggerOutput diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index 33e64da7ad..13c7e1709d 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -114,4 +114,10 @@ export default class AccountAPI extends BaseAPI { }) }, opts) } + + async self(opts: APIRequestOpts = { status: 200 }) { + return this.doRequest(() => { + return this.client.get(`/api/auth/self`) + }, opts) + } } diff --git a/qa-core/src/account-api/api/apis/AuthAPI.ts b/qa-core/src/account-api/api/apis/AuthAPI.ts index 50345c891b..304b13db57 100644 --- a/qa-core/src/account-api/api/apis/AuthAPI.ts +++ b/qa-core/src/account-api/api/apis/AuthAPI.ts @@ -2,6 +2,7 @@ import { Response } from "node-fetch" import AccountInternalAPIClient from "../AccountInternalAPIClient" import { APIRequestOpts } from "../../../types" import BaseAPI from "./BaseAPI" +import { Header } from "@budibase/backend-core" export default class AuthAPI extends BaseAPI { client: AccountInternalAPIClient @@ -27,4 +28,41 @@ export default class AuthAPI extends BaseAPI { return [res, cookie] }, opts) } + + async logout(opts: APIRequestOpts = { status: 200 }) { + return this.doRequest(() => { + return this.client.post(`/api/auth/logout`) + }, opts) + } + + async resetPassword( + email: string, + opts: APIRequestOpts = { status: 200 } + ): Promise<[Response, string]> { + return this.doRequest(async () => { + const [response] = await this.client.post(`/api/auth/reset`, { + body: { email }, + headers: { + [Header.RETURN_RESET_PASSWORD_CODE]: "1", + }, + }) + const code = response.headers.get(Header.RESET_PASSWORD_CODE) + return [response, code] + }, opts) + } + + async resetPasswordUpdate( + resetCode: string, + password: string, + opts: APIRequestOpts = { status: 200 } + ) { + return this.doRequest(() => { + return this.client.post(`/api/auth/reset/update`, { + body: { + resetCode: resetCode, + password: password, + }, + }) + }, opts) + } } diff --git a/qa-core/src/account-api/tests/accounts/accounts.cloud.internal.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.cloud.internal.spec.ts index 6c1d7eacac..56f9110322 100644 --- a/qa-core/src/account-api/tests/accounts/accounts.cloud.internal.spec.ts +++ b/qa-core/src/account-api/tests/accounts/accounts.cloud.internal.spec.ts @@ -1,6 +1,7 @@ import TestConfiguration from "../../config/TestConfiguration" import * as fixtures from "../../fixtures" import { generator } from "../../../shared" +import { Hosting } from "@budibase/types" describe("Account Internal Operations", () => { const config = new TestConfiguration() @@ -20,7 +21,9 @@ describe("Account Internal Operations", () => { // Create new account const [_, account] = await config.api.accounts.create({ - ...fixtures.accounts.generateAccount(), + ...fixtures.accounts.generateAccount({ + hosting: Hosting.CLOUD, + }), }) // New account can be deleted diff --git a/qa-core/src/account-api/tests/accounts/accounts.cloud.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.cloud.spec.ts index e3a4d4eacf..0969b72cf9 100644 --- a/qa-core/src/account-api/tests/accounts/accounts.cloud.spec.ts +++ b/qa-core/src/account-api/tests/accounts/accounts.cloud.spec.ts @@ -1,6 +1,7 @@ import TestConfiguration from "../../config/TestConfiguration" import * as fixtures from "../../fixtures" import { generator } from "../../../shared" +import { Hosting } from "@budibase/types" describe("Accounts", () => { const config = new TestConfiguration() @@ -16,7 +17,9 @@ describe("Accounts", () => { it("performs signup and deletion flow", async () => { await config.doInNewState(async () => { // Create account - const createAccountRequest = fixtures.accounts.generateAccount() + const createAccountRequest = fixtures.accounts.generateAccount({ + hosting: Hosting.CLOUD, + }) const email = createAccountRequest.email const tenantId = createAccountRequest.tenantId @@ -42,9 +45,16 @@ describe("Accounts", () => { // Send the verification request await config.accountsApi.accounts.verifyAccount(code!) + // Verify self response is unauthorized + await config.api.accounts.self({ status: 403 }) + // Can now log in to the account await config.loginAsAccount(createAccountRequest) + // Verify self response matches account + const [selfRes, selfBody] = await config.api.accounts.self() + expect(selfBody.email).toBe(email) + // Delete account await config.api.accounts.deleteCurrentAccount() diff --git a/qa-core/src/account-api/tests/auth/auth.cloud.spec.ts b/qa-core/src/account-api/tests/auth/auth.cloud.spec.ts new file mode 100644 index 0000000000..075a52bef4 --- /dev/null +++ b/qa-core/src/account-api/tests/auth/auth.cloud.spec.ts @@ -0,0 +1,46 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" +import { generator } from "../../../shared" +import { Hosting } from "@budibase/types" + +describe("Password Management", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("performs password reset flow", async () => { + // Create account + const createAccountRequest = fixtures.accounts.generateAccount({ + hosting: Hosting.CLOUD, + }) + await config.api.accounts.create(createAccountRequest, { autoVerify: true }) + + // Request password reset to get code + const [_, code] = await config.api.auth.resetPassword( + createAccountRequest.email + ) + + // Change password using code + const password = generator.string() + await config.api.auth.resetPasswordUpdate(code, password) + + // Login using the new password + await config.api.auth.login(createAccountRequest.email, password) + + // Logout of account + await config.api.auth.logout() + + // Cannot log in using old password + await config.api.auth.login( + createAccountRequest.email, + createAccountRequest.password, + { status: 403 } + ) + }) +}) diff --git a/yarn.lock b/yarn.lock index a0d71c7763..a903ea6778 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15278,10 +15278,10 @@ jest-snapshot@^29.6.2: pretty-format "^29.6.2" semver "^7.5.3" -jest-util@^29.0.0: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" - integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== +jest-util@^29.0.0, jest-util@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" + integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== dependencies: "@jest/types" "^29.6.1" "@types/node" "*" @@ -15302,18 +15302,6 @@ jest-util@^29.4.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" - integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-validate@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082"