mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Increase minimum password length to 12 characters (#5712)
This commit is contained in:
parent
c4962a2463
commit
4a4e832d3a
54 changed files with 460 additions and 196 deletions
4
.github/workflows/fleet-and-orbit.yml
vendored
4
.github/workflows/fleet-and-orbit.yml
vendored
|
|
@ -92,7 +92,7 @@ jobs:
|
|||
make db-reset
|
||||
./build/fleet serve --dev --dev_license 1>./fleet_log/stdout.log 2>./fleet_log/stderr.log &
|
||||
./build/fleetctl config set --address http://localhost:1337 --tls-skip-verify
|
||||
until ./build/fleetctl setup --email admin@example.com --name Admin --password admin123# --org-name Example
|
||||
until ./build/fleetctl setup --email admin@example.com --name Admin --password preview1337# --org-name Example
|
||||
do
|
||||
echo "Retrying setup in 5s..."
|
||||
sleep 5
|
||||
|
|
@ -146,7 +146,7 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
run: |
|
||||
./build/fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
until ./build/fleetctl login --email admin@example.com --password admin123#
|
||||
until ./build/fleetctl login --email admin@example.com --password preview1337#
|
||||
do
|
||||
echo "Retrying in 10s..."
|
||||
sleep 10
|
||||
|
|
|
|||
2
.github/workflows/integration.yml
vendored
2
.github/workflows/integration.yml
vendored
|
|
@ -128,7 +128,7 @@ jobs:
|
|||
run: |
|
||||
chmod +x ./build/fleetctl
|
||||
./build/fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
until ./build/fleetctl login --email admin@example.com --password admin123#
|
||||
until ./build/fleetctl login --email admin@example.com --password preview1337#
|
||||
do
|
||||
echo "Retrying in 5s..."
|
||||
sleep 5
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -246,9 +246,9 @@ e2e-reset-db:
|
|||
|
||||
e2e-setup:
|
||||
./build/fleetctl config set --context e2e --address https://localhost:8642 --tls-skip-verify true
|
||||
./build/fleetctl setup --context e2e --email=admin@example.com --password=user123# --org-name='Fleet Test' --name Admin
|
||||
./build/fleetctl user create --context e2e --email=maintainer@example.com --name maintainer --password=user123# --global-role=maintainer
|
||||
./build/fleetctl user create --context e2e --email=observer@example.com --name observer --password=user123# --global-role=observer
|
||||
./build/fleetctl setup --context e2e --email=admin@example.com --password=password123# --org-name='Fleet Test' --name Admin
|
||||
./build/fleetctl user create --context e2e --email=maintainer@example.com --name maintainer --password=password123# --global-role=maintainer
|
||||
./build/fleetctl user create --context e2e --email=observer@example.com --name observer --password=password123# --global-role=observer
|
||||
./build/fleetctl user create --context e2e --email=sso_user@example.com --name "SSO user" --sso=true
|
||||
|
||||
e2e-serve-free: e2e-reset-db
|
||||
|
|
|
|||
1
changes/issue-5477-password-length
Normal file
1
changes/issue-5477-password-length
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Increase minimum password length to 12 characters
|
||||
|
|
@ -222,7 +222,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
|
|||
const (
|
||||
address = "https://localhost:8412"
|
||||
email = "admin@example.com"
|
||||
password = "admin123#"
|
||||
password = "preview1337#"
|
||||
)
|
||||
|
||||
fleetClient, err := service.NewClient(address, true, "", "")
|
||||
|
|
|
|||
|
|
@ -153,6 +153,8 @@ func createUserCommand() *cli.Command {
|
|||
// Only set the password reset flag if SSO is not enabled and user is not API-only. Otherwise
|
||||
// the user will be stuck in a bad state and not be able to log in.
|
||||
force_reset := !sso && !apiOnly
|
||||
|
||||
// password requirements are validated as part of `CreateUser`
|
||||
err = client.CreateUser(fleet.UserPayload{
|
||||
Password: &password,
|
||||
Email: &email,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -49,6 +51,8 @@ func (e *notFoundError) Error() string {
|
|||
func TestUserCreateForcePasswordReset(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
pwd := test.GoodPassword
|
||||
|
||||
ds.InviteByEmailFunc = func(ctx context.Context, email string) (*fleet.Invite, error) {
|
||||
return nil, ¬FoundError{}
|
||||
}
|
||||
|
|
@ -65,7 +69,7 @@ func TestUserCreateForcePasswordReset(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "api-only",
|
||||
args: []string{"--email", "bar@example.com", "--password", "p4ssw0rd.", "--name", "bar", "--api-only"},
|
||||
args: []string{"--email", "bar@example.com", "--password", pwd, "--name", "bar", "--api-only"},
|
||||
expectedAdminForcePasswordReset: false,
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +79,7 @@ func TestUserCreateForcePasswordReset(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "non-sso-non-api-only",
|
||||
args: []string{"--email", "zoo@example.com", "--password", "p4ssw0rd.", "--name", "zoo"},
|
||||
args: []string{"--email", "zoo@example.com", "--password", pwd, "--name", "zoo"},
|
||||
expectedAdminForcePasswordReset: true,
|
||||
},
|
||||
} {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
import CONSTANTS from "../../../support/constants";
|
||||
|
||||
const {
|
||||
GOOD_PASSWORD,
|
||||
BAD_PASSWORD_LENGTH,
|
||||
BAD_PASSWORD_NO_NUMBER,
|
||||
BAD_PASSWORD_NO_SYMBOL,
|
||||
} = CONSTANTS;
|
||||
|
||||
describe("Activate user flow", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -35,7 +44,7 @@ describe("Activate user flow", () => {
|
|||
cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.logout();
|
||||
// Retrieves user invite in email
|
||||
const inviteLink = {};
|
||||
const inviteLink = { url: "" };
|
||||
const regex = /\/login\/invites\/[a-zA-Z0-9=?%&@._-]*/gm;
|
||||
cy.getEmails().then((response) => {
|
||||
expect(response.body.items[0].To[0]).to.have.property("Domain");
|
||||
|
|
@ -54,17 +63,40 @@ describe("Activate user flow", () => {
|
|||
.type("Ash Ketchum");
|
||||
cy.findByLabelText(/^password$/i)
|
||||
.click()
|
||||
.type("#pikachu1");
|
||||
.type(BAD_PASSWORD_LENGTH);
|
||||
cy.findByLabelText(/confirm password/i)
|
||||
.click()
|
||||
.type("#pikachu1");
|
||||
.type(BAD_PASSWORD_LENGTH);
|
||||
cy.findByRole("button", { name: /submit/i }).click();
|
||||
});
|
||||
cy.findByText(/password does not meet required criteria/i).should(
|
||||
"exist"
|
||||
);
|
||||
cy.getAttached("#password").clear().type(BAD_PASSWORD_NO_NUMBER);
|
||||
cy.getAttached("#password_confirmation")
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_NUMBER);
|
||||
cy.findByRole("button", { name: /submit/i }).click();
|
||||
cy.findByText(/password does not meet required criteria/i).should(
|
||||
"exist"
|
||||
);
|
||||
cy.getAttached("#password").clear().type(BAD_PASSWORD_NO_SYMBOL);
|
||||
cy.getAttached("#password_confirmation")
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_SYMBOL);
|
||||
cy.findByRole("button", { name: /submit/i }).click();
|
||||
cy.findByText(/password does not meet required criteria/i).should(
|
||||
"exist"
|
||||
);
|
||||
cy.getAttached("#password").clear().type(GOOD_PASSWORD);
|
||||
cy.getAttached("#password_confirmation").clear().type(GOOD_PASSWORD);
|
||||
cy.findByRole("button", { name: /submit/i }).click();
|
||||
|
||||
cy.getAttached(".login-form").within(() => {
|
||||
cy.findByLabelText(/email/i).clear().type("ash@example.com");
|
||||
cy.findByLabelText(/^password$/i)
|
||||
.click()
|
||||
.type("#pikachu1");
|
||||
.type(GOOD_PASSWORD);
|
||||
cy.findByRole("button", { name: /login/i }).click();
|
||||
});
|
||||
Cypress.session.clearAllSavedSessions(); // Switch back to admin user
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
import CONSTANTS from "../,,/../../../support/constants";
|
||||
|
||||
const {
|
||||
GOOD_PASSWORD,
|
||||
BAD_PASSWORD_LENGTH,
|
||||
BAD_PASSWORD_NO_NUMBER,
|
||||
BAD_PASSWORD_NO_SYMBOL,
|
||||
} = CONSTANTS;
|
||||
|
||||
describe("Manage users flow", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -32,7 +41,6 @@ describe("Manage users flow", () => {
|
|||
cy.contains("button:enabled", /create user/i).click();
|
||||
cy.findByPlaceholderText("Full name").type("New Name");
|
||||
cy.findByPlaceholderText("Email").type("new-user@example.com");
|
||||
cy.findByPlaceholderText("Password").type("user123#");
|
||||
cy.getAttached(
|
||||
".create-user-form__form-field--global-role > .Select"
|
||||
).click();
|
||||
|
|
@ -41,6 +49,28 @@ describe("Manage users flow", () => {
|
|||
cy.findByText(/maintainer/i).click();
|
||||
}
|
||||
);
|
||||
cy.findByPlaceholderText("Password").clear().type(BAD_PASSWORD_LENGTH);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /create/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_NUMBER);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /create/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_NUMBER);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /create/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(GOOD_PASSWORD);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /create/i)
|
||||
.click();
|
||||
|
|
@ -64,6 +94,28 @@ describe("Manage users flow", () => {
|
|||
cy.findByText(/admin/i).click();
|
||||
}
|
||||
);
|
||||
cy.findByLabelText("Password").clear().type(BAD_PASSWORD_LENGTH);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /save/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_NUMBER);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /save/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(BAD_PASSWORD_NO_SYMBOL);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /save/i)
|
||||
.click();
|
||||
cy.findByText(/password must meet the criteria below/i).should("exist");
|
||||
cy.findByLabelText(/password must meet the criteria below/i)
|
||||
.clear()
|
||||
.type(GOOD_PASSWORD);
|
||||
cy.getAttached(".create-user-form__btn-wrap")
|
||||
.contains("button", /save/i)
|
||||
.click();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Sessions", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -8,7 +12,7 @@ describe("Sessions", () => {
|
|||
cy.getAttached(".login-form__forgot-link").should("exist");
|
||||
// Log in
|
||||
cy.getAttached("input").first().type("admin@example.com");
|
||||
cy.getAttached("input").last().type("user123#");
|
||||
cy.getAttached("input").last().type(GOOD_PASSWORD);
|
||||
cy.getAttached("button").click();
|
||||
// Verify dashboard
|
||||
cy.url().should("include", "/dashboard");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
import CONSTANTS from "../../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
const enable_idp_login = true;
|
||||
|
||||
describe("SSO Sessions", () => {
|
||||
beforeEach(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -5,13 +11,13 @@ describe("SSO Sessions", () => {
|
|||
});
|
||||
it("non-SSO user can login with username/password", () => {
|
||||
cy.login();
|
||||
cy.setupSSO((enable_idp_login = true));
|
||||
cy.setupSSO(enable_idp_login);
|
||||
cy.logout();
|
||||
cy.visit("/");
|
||||
cy.getAttached(".login-form__forgot-link").should("exist");
|
||||
// Log in
|
||||
cy.getAttached("input").first().type("admin@example.com");
|
||||
cy.getAttached("input").last().type("user123#");
|
||||
cy.getAttached("input").last().type(GOOD_PASSWORD);
|
||||
cy.contains("button", "Login").click();
|
||||
// Verify dashboard
|
||||
cy.url().should("include", "/dashboard");
|
||||
|
|
@ -23,7 +29,7 @@ describe("SSO Sessions", () => {
|
|||
});
|
||||
it("can login via SSO", () => {
|
||||
cy.login();
|
||||
cy.setupSSO((enable_idp_login = true));
|
||||
cy.setupSSO(enable_idp_login);
|
||||
cy.logout();
|
||||
cy.visit("/");
|
||||
// Log in
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Setup", () => {
|
||||
// Different than normal beforeEach because we don't run the fleetctl setup.
|
||||
beforeEach(() => {
|
||||
|
|
@ -20,11 +24,11 @@ describe("Setup", () => {
|
|||
|
||||
cy.findByPlaceholderText(/^password/i)
|
||||
.first()
|
||||
.type("admin123#");
|
||||
.type(GOOD_PASSWORD);
|
||||
|
||||
cy.findByPlaceholderText(/confirm password/i)
|
||||
.last()
|
||||
.type("admin123#");
|
||||
.type(GOOD_PASSWORD);
|
||||
|
||||
cy.contains("button:enabled", /next/i).click();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
const getConfig = {
|
||||
org_info: {
|
||||
org_name: "Fleet Test",
|
||||
|
|
@ -286,7 +290,7 @@ describe(
|
|||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays intended admin top navigation", () => {
|
||||
|
|
@ -313,7 +317,7 @@ describe(
|
|||
});
|
||||
describe("Dashboard", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays cards for all platforms", () => {
|
||||
|
|
@ -412,7 +416,7 @@ describe(
|
|||
});
|
||||
describe("Manage hosts page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/manage");
|
||||
});
|
||||
it("verifies teams is disabled on Manage Host page", () => {
|
||||
|
|
@ -435,7 +439,7 @@ describe(
|
|||
});
|
||||
describe("Host details tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/1");
|
||||
});
|
||||
it("verifies teams is disabled on Host Details page", () => {
|
||||
|
|
@ -459,7 +463,7 @@ describe(
|
|||
});
|
||||
describe("Manage software page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.intercept("GET", "/api/latest/fleet/config", getConfig).as(
|
||||
"getIntegrations"
|
||||
);
|
||||
|
|
@ -586,7 +590,7 @@ describe(
|
|||
});
|
||||
describe("Query pages", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/queries/manage");
|
||||
});
|
||||
it("allows admin add a new query", () => {
|
||||
|
|
@ -637,7 +641,7 @@ describe(
|
|||
});
|
||||
describe("Manage policies page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/policies/manage");
|
||||
});
|
||||
it("allows admin to click 'Manage automations' button", () => {
|
||||
|
|
@ -683,7 +687,7 @@ describe(
|
|||
return false;
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/settings/users");
|
||||
});
|
||||
it("hides access team settings", () => {
|
||||
|
|
@ -727,7 +731,7 @@ describe(
|
|||
});
|
||||
describe("User profile page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/profile");
|
||||
});
|
||||
it("verifies teams is disabled for the Profile page", () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe(
|
||||
"Free tier - Maintainer user",
|
||||
{
|
||||
|
|
@ -21,7 +25,7 @@ describe(
|
|||
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays intended global maintainer top navigation", () => {
|
||||
|
|
@ -39,7 +43,7 @@ describe(
|
|||
});
|
||||
describe("Dashboard", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays cards for all platforms", () => {
|
||||
|
|
@ -138,7 +142,7 @@ describe(
|
|||
});
|
||||
describe("Manage hosts page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/manage");
|
||||
});
|
||||
it("verifies maintainer is on the Manage Hosts page", () => {
|
||||
|
|
@ -164,7 +168,7 @@ describe(
|
|||
});
|
||||
describe("Host details tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/1");
|
||||
});
|
||||
it("verifies teams is disabled", () => {
|
||||
|
|
@ -194,7 +198,7 @@ describe(
|
|||
});
|
||||
describe("Query pages", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/queries/manage");
|
||||
});
|
||||
it("allows maintainer to add a new query", () => {
|
||||
|
|
@ -247,7 +251,7 @@ describe(
|
|||
});
|
||||
describe("Manage policies page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/policies/manage");
|
||||
});
|
||||
it("hides manage automations from maintainer", () => {
|
||||
|
|
@ -299,7 +303,7 @@ describe(
|
|||
});
|
||||
describe("Manage packs page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/packs/manage");
|
||||
});
|
||||
it("allows maintainer to create a pack", () => {
|
||||
|
|
@ -329,7 +333,7 @@ describe(
|
|||
});
|
||||
describe("User profile page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/profile");
|
||||
});
|
||||
it("verifies teams is disabled for the Profile page", () => {
|
||||
|
|
@ -356,7 +360,7 @@ describe(
|
|||
return false;
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
it("verifies maintainer does not have access to settings", () => {
|
||||
cy.findByText(/settings/i).should("not.exist");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Free tier - Observer user", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -16,7 +20,7 @@ describe("Free tier - Observer user", () => {
|
|||
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays intended global observer top navigation", () => {
|
||||
|
|
@ -34,7 +38,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Dashboard", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/dashboard");
|
||||
});
|
||||
it("displays cards for all platforms", () => {
|
||||
|
|
@ -133,7 +137,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Manage hosts page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/manage");
|
||||
});
|
||||
it("verifies teams is disabled on Manage Host page", () => {
|
||||
|
|
@ -151,7 +155,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Host details page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/1");
|
||||
});
|
||||
it("verifies teams is disabled on Host Details page", () => {
|
||||
|
|
@ -169,7 +173,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Manage software page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/software/manage");
|
||||
});
|
||||
it("hides manage automations button", () => {
|
||||
|
|
@ -182,7 +186,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Query page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/queries/manage");
|
||||
});
|
||||
it("hides create a query button", () => {
|
||||
|
|
@ -205,7 +209,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("Manage policies page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/policies/manage");
|
||||
});
|
||||
it("hides manage automations button", () => {
|
||||
|
|
@ -235,7 +239,7 @@ describe("Free tier - Observer user", () => {
|
|||
});
|
||||
describe("User profile page", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/profile");
|
||||
});
|
||||
it("verifies teams is disabled for the Profile page", () => {
|
||||
|
|
@ -262,7 +266,7 @@ describe("Free tier - Observer user", () => {
|
|||
return false;
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
it("should restrict navigation according to role-based access controls", () => {
|
||||
cy.findByText(/settings/i).should("not.exist");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
const getConfig = {
|
||||
org_info: {
|
||||
org_name: "Fleet Test",
|
||||
|
|
@ -281,7 +285,7 @@ describe("Premium tier - Global Admin user", () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anna@organization.com", "user123#");
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => cy.visit("/dashboard"));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Premium tier - Maintainer user", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -15,7 +19,7 @@ describe("Premium tier - Maintainer user", () => {
|
|||
|
||||
describe("Global maintainer", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("mary@organization.com", "user123#");
|
||||
cy.loginWithCySession("mary@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => cy.visit("/dashboard"));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Premium tier - Observer user", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -16,7 +20,7 @@ describe("Premium tier - Observer user", () => {
|
|||
|
||||
describe("Global observer", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("oliver@organization.com", "user123#");
|
||||
cy.loginWithCySession("oliver@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => cy.visit("/dashboard"));
|
||||
|
|
@ -241,7 +245,7 @@ describe("Premium tier - Observer user", () => {
|
|||
|
||||
describe("Team observer", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("toni@organization.com", "user123#");
|
||||
cy.loginWithCySession("toni@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Nav restrictions", () => {
|
||||
it("should restrict navigation according to role-based access controls", () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Premium tier - Team Admin user", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -15,7 +19,7 @@ describe("Premium tier - Team Admin user", () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("anita@organization.com", "user123#");
|
||||
cy.loginWithCySession("anita@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => cy.visit("/dashboard"));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import CONSTANTS from "../../support/constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
describe("Premium tier - Team observer/maintainer user", () => {
|
||||
before(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
|
@ -15,7 +19,7 @@ describe("Premium tier - Team observer/maintainer user", () => {
|
|||
});
|
||||
describe("Team maintainer and team observer", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("marco@organization.com", "user123#");
|
||||
cy.loginWithCySession("marco@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Navigation", () => {
|
||||
beforeEach(() => cy.visit("/dashboard"));
|
||||
|
|
@ -132,7 +136,7 @@ describe("Premium tier - Team observer/maintainer user", () => {
|
|||
});
|
||||
describe("Team observer", () => {
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("marco@organization.com", "user123#");
|
||||
cy.loginWithCySession("marco@organization.com", GOOD_PASSWORD);
|
||||
});
|
||||
describe("Manage hosts page", () => {
|
||||
it("should render elements according to role-based access controls", () => {
|
||||
|
|
@ -200,7 +204,7 @@ describe("Premium tier - Team observer/maintainer user", () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginWithCySession("marco@organization.com", "user123#");
|
||||
cy.loginWithCySession("marco@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/hosts/manage");
|
||||
});
|
||||
describe("Manage hosts page", () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import "@testing-library/cypress/add-commands";
|
||||
import "cypress-wait-until";
|
||||
import CONSTANTS from "./constants";
|
||||
|
||||
const { GOOD_PASSWORD } = CONSTANTS;
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
|
|
@ -38,7 +41,7 @@ Cypress.Commands.add("setup", () => {
|
|||
|
||||
Cypress.Commands.add("login", (email, password) => {
|
||||
email ||= "admin@example.com";
|
||||
password ||= "user123#";
|
||||
password ||= GOOD_PASSWORD;
|
||||
cy.request("POST", "/api/latest/fleet/login", { email, password }).then(
|
||||
(resp) => {
|
||||
window.localStorage.setItem("FLEET::auth_token", resp.body.token);
|
||||
|
|
@ -48,7 +51,7 @@ Cypress.Commands.add("login", (email, password) => {
|
|||
|
||||
Cypress.Commands.add("loginWithCySession", (email, password) => {
|
||||
email ||= "admin@example.com";
|
||||
password ||= "user123#";
|
||||
password ||= GOOD_PASSWORD;
|
||||
cy.session([email, password], () => {
|
||||
cy.request("POST", "/api/latest/fleet/login", { email, password }).then(
|
||||
(resp) => {
|
||||
|
|
@ -154,6 +157,7 @@ Cypress.Commands.add("seedSchedule", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
Cypress.Commands.add("seedPacks", () => {
|
||||
const packs = [
|
||||
{
|
||||
|
|
@ -377,7 +381,7 @@ Cypress.Commands.add("seedFigma", () => {
|
|||
|
||||
Cypress.Commands.add("addUser", (options = {}) => {
|
||||
let { password, email, globalRole } = options;
|
||||
password ||= "test123#";
|
||||
password ||= GOOD_PASSWORD;
|
||||
email ||= `admin@example.com`;
|
||||
globalRole ||= "admin";
|
||||
|
||||
|
|
|
|||
11
cypress/support/constants.ts
Normal file
11
cypress/support/constants.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const GOOD_PASSWORD = "password123#";
|
||||
const BAD_PASSWORD_LENGTH = "password12#";
|
||||
const BAD_PASSWORD_NO_NUMBER = "password####";
|
||||
const BAD_PASSWORD_NO_SYMBOL = "password1234";
|
||||
|
||||
export default {
|
||||
GOOD_PASSWORD,
|
||||
BAD_PASSWORD_LENGTH,
|
||||
BAD_PASSWORD_NO_NUMBER,
|
||||
BAD_PASSWORD_NO_SYMBOL,
|
||||
};
|
||||
6
cypress/support/index.d.ts
vendored
6
cypress/support/index.d.ts
vendored
|
|
@ -37,7 +37,7 @@ declare namespace Cypress {
|
|||
/**
|
||||
* Custom command to add new policies by default.
|
||||
*/
|
||||
seedPolicies(): Chainable<Element>;
|
||||
seedPolicies(teamName?: string): Chainable<Element>;
|
||||
|
||||
/**
|
||||
* Custom command to add a new user in Fleet (via fleetctl).
|
||||
|
|
@ -72,7 +72,7 @@ declare namespace Cypress {
|
|||
/**
|
||||
* Custom command to get the emails handled by the Mailhog server.
|
||||
*/
|
||||
getEmails(): Chainable<Response>;
|
||||
getEmails(): Chainable;
|
||||
|
||||
/**
|
||||
* Custom command to seed the Free tier teams/users.
|
||||
|
|
@ -104,7 +104,7 @@ declare namespace Cypress {
|
|||
* NOTE: login() command is required before this, as it will make authenticated
|
||||
* requests.
|
||||
*/
|
||||
addDockerHost(): Chainable;
|
||||
addDockerHost(teamName?: string): Chainable;
|
||||
|
||||
/**
|
||||
* Custom command to stop any running Docker hosts.
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ text or HTML as mentioned before.
|
|||
value={password || ""}
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
blockAutoComplete
|
||||
tooltip={`\
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class ChangePasswordForm extends Component {
|
|||
label="New password"
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
/>
|
||||
<InputField
|
||||
|
|
|
|||
|
|
@ -123,9 +123,9 @@ describe("ChangePasswordForm - component", () => {
|
|||
render(<ChangePasswordForm {...props} />);
|
||||
|
||||
const expectedFormData = {
|
||||
old_password: "p@ssw0rd",
|
||||
new_password: "p@ssw0rd1",
|
||||
new_password_confirmation: "p@ssw0rd1",
|
||||
old_password: "password123#",
|
||||
new_password: "password123!",
|
||||
new_password_confirmation: "password123!",
|
||||
};
|
||||
|
||||
// when
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class ConfirmInviteForm extends Component {
|
|||
placeholder="Password"
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class AdminDetails extends Component {
|
|||
type="password"
|
||||
tabIndex={tabIndex}
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
|
|
|
|||
|
|
@ -83,8 +83,11 @@ describe("AdminDetails - form", () => {
|
|||
screen.getByRole("textbox", { name: "Email" }),
|
||||
"hi@gnar.dog"
|
||||
);
|
||||
userEvent.type(screen.getByPlaceholderText("Password"), "p@ssw0rd");
|
||||
userEvent.type(screen.getByPlaceholderText("Confirm password"), "p@ssw0rd");
|
||||
userEvent.type(screen.getByPlaceholderText("Password"), "password123#");
|
||||
userEvent.type(
|
||||
screen.getByPlaceholderText("Confirm password"),
|
||||
"password123#"
|
||||
);
|
||||
userEvent.type(
|
||||
screen.getByRole("textbox", { name: "Full name" }),
|
||||
"Gnar Dog"
|
||||
|
|
@ -94,8 +97,8 @@ describe("AdminDetails - form", () => {
|
|||
expect(onSubmitSpy).toHaveBeenCalledWith({
|
||||
email: "hi@gnar.dog",
|
||||
name: "Gnar Dog",
|
||||
password: "p@ssw0rd",
|
||||
password_confirmation: "p@ssw0rd",
|
||||
password: "password123#",
|
||||
password_confirmation: "password123#",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ class RegistrationForm extends Component {
|
|||
/>
|
||||
</div>
|
||||
<div className={confirmationContainerClass}>
|
||||
<h2>Success</h2>
|
||||
<h2>Confirm configuration</h2>
|
||||
<ConfirmationPage
|
||||
formData={formData}
|
||||
handleSubmit={onSubmitConfirmation}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,6 @@ describe("RegistrationForm - component", () => {
|
|||
container.querySelectorAll(".user-registration__container--confirmation")
|
||||
.length
|
||||
).toEqual(1);
|
||||
expect(screen.getByText("Success")).toBeInTheDocument();
|
||||
expect(screen.getByText("Confirm configuration")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class ResetPasswordForm extends Component {
|
|||
className={`${baseClass}__input`}
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
|
|||
import ResetPasswordForm from "./ResetPasswordForm";
|
||||
|
||||
describe("ResetPasswordForm - component", () => {
|
||||
const newPassword = "p@ssw0rd";
|
||||
const newPassword = "password123!";
|
||||
const submitSpy = jest.fn();
|
||||
it("renders correctly", () => {
|
||||
render(<ResetPasswordForm handleSubmit={submitSpy} />);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const SYMBOL_PRESENT = /\W+/;
|
|||
|
||||
export default (password = "") => {
|
||||
return (
|
||||
password.length >= 7 &&
|
||||
password.length >= 12 &&
|
||||
LETTER_PRESENT.test(password) &&
|
||||
NUMBER_PRESENT.test(password) &&
|
||||
SYMBOL_PRESENT.test(password)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ describe("validPassword", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("is valid if the password is at least 7 characters and includes a number and a symbol", () => {
|
||||
it("is valid if the password is at least 12 characters and includes a number and a symbol", () => {
|
||||
const validPasswords = [
|
||||
"p@assw0rd",
|
||||
"p@assw0rd123",
|
||||
"This should be v4lid!",
|
||||
"admin123.",
|
||||
"admin123.pass",
|
||||
"pRZ'bW,6'6o}HnpL62",
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ const ConfirmInvitePage = ({
|
|||
const { create } = usersAPI;
|
||||
const { LOGIN } = paths;
|
||||
|
||||
setUserErrors({});
|
||||
|
||||
try {
|
||||
await create(formData);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const LoginPreviewPage = ({ router }: ILoginPreviewPageProps): JSX.Element => {
|
|||
if (isPreviewMode) {
|
||||
onSubmit({
|
||||
email: "admin@example.com",
|
||||
password: "admin123#",
|
||||
password: "preview1337#",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ const RegistrationPage = ({ router }: IRegistrationPageProps) => {
|
|||
setCurrentUser(user);
|
||||
setAvailableTeams(available_teams);
|
||||
return router.push(MANAGE_HOSTS);
|
||||
} catch (response) {
|
||||
console.error(response);
|
||||
} catch (error) {
|
||||
// TODO: Alert user to server errors
|
||||
console.log(error);
|
||||
setPage(1);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const ResetPasswordPage = ({ location, router }: IResetPasswordPageProps) => {
|
|||
|
||||
return (
|
||||
<AuthenticationFormWrapper>
|
||||
<StackedWhiteBoxes leadText="Create a new password. Your new password must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)">
|
||||
<StackedWhiteBoxes leadText="Create a new password. Your new password must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)">
|
||||
<ResetPasswordForm
|
||||
handleSubmit={onSubmit}
|
||||
onChangeFunc={onResetErrors}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,12 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
|
|||
setCreateUserErrors({
|
||||
email: "A user with this email address already exists",
|
||||
});
|
||||
} else if (
|
||||
userErrors.data.errors[0].reason.includes("required criteria")
|
||||
) {
|
||||
setCreateUserErrors({
|
||||
password: "Password must meet the criteria below",
|
||||
});
|
||||
} else {
|
||||
renderFlash("error", "Could not create user. Please try again.");
|
||||
}
|
||||
|
|
@ -269,6 +275,12 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
|
|||
setCreateUserErrors({
|
||||
email: "A user with this email address already exists",
|
||||
});
|
||||
} else if (
|
||||
userErrors.data.errors[0].reason.includes("required criteria")
|
||||
) {
|
||||
setCreateUserErrors({
|
||||
password: "Password must meet the criteria below",
|
||||
});
|
||||
} else {
|
||||
renderFlash("error", "Could not create user. Please try again.");
|
||||
}
|
||||
|
|
@ -298,6 +310,12 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
|
|||
setEditUserErrors({
|
||||
email: "A user with this email address already exists",
|
||||
});
|
||||
} else if (
|
||||
userErrors.data.errors[0].reason.includes("required criteria")
|
||||
) {
|
||||
setEditUserErrors({
|
||||
password: "Password must meet the criteria below",
|
||||
});
|
||||
} else {
|
||||
renderFlash(
|
||||
"error",
|
||||
|
|
@ -326,6 +344,12 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
|
|||
setEditUserErrors({
|
||||
email: "A user with this email address already exists",
|
||||
});
|
||||
} else if (
|
||||
userErrors.data.errors[0].reason.includes("required criteria")
|
||||
) {
|
||||
setEditUserErrors({
|
||||
password: "Password must meet the criteria below",
|
||||
});
|
||||
}
|
||||
renderFlash(
|
||||
"error",
|
||||
|
|
@ -355,6 +379,12 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
|
|||
setEditUserErrors({
|
||||
email: "A user with this email address already exists",
|
||||
});
|
||||
} else if (
|
||||
userErrors.data.errors[0].reason.includes("required criteria")
|
||||
) {
|
||||
setEditUserErrors({
|
||||
password: "Password must meet the criteria below",
|
||||
});
|
||||
} else {
|
||||
renderFlash(
|
||||
"error",
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ const UserForm = ({
|
|||
value={formData.password || ""}
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
blockAutoComplete
|
||||
/>
|
||||
|
|
@ -507,7 +507,7 @@ const UserForm = ({
|
|||
value={formData.password || ""}
|
||||
type="password"
|
||||
hint={[
|
||||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
"Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
blockAutoComplete
|
||||
tooltip={`\
|
||||
|
|
|
|||
|
|
@ -258,8 +258,19 @@ func (p UserPayload) User(keySize, cost int) (*User, error) {
|
|||
Email: *p.Email,
|
||||
Teams: []UserTeam{},
|
||||
}
|
||||
if err := user.SetPassword(*p.Password, keySize, cost); err != nil {
|
||||
return nil, err
|
||||
|
||||
if (p.SSOInvite != nil && *p.SSOInvite) || (p.SSOEnabled != nil && *p.SSOEnabled) {
|
||||
user.SSOEnabled = true
|
||||
// SSO user requires a stand-in password to satisfy `NOT NULL` constraint
|
||||
err := user.SetFakePassword(keySize, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := user.SetPassword(*p.Password, keySize, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// add optional fields
|
||||
|
|
@ -297,24 +308,22 @@ func (u *User) ValidatePassword(password string) error {
|
|||
}
|
||||
|
||||
func (u *User) SetPassword(plaintext string, keySize, cost int) error {
|
||||
salt, err := server.GenerateRandomText(keySize)
|
||||
if err != nil {
|
||||
if err := ValidatePasswordRequirements(plaintext); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
withSalt := []byte(fmt.Sprintf("%s%s", plaintext, salt))
|
||||
hashed, err := bcrypt.GenerateFromPassword(withSalt, cost)
|
||||
hashed, salt, err := saltAndHashPassword(keySize, plaintext, cost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Salt = salt
|
||||
u.Password = hashed
|
||||
u.Salt = salt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Requirements for user password:
|
||||
// at least 7 character length
|
||||
// ValidatePasswordRequirements checks the provided password against the following requirements:
|
||||
// at least 12 character length
|
||||
// at least 1 symbol
|
||||
// at least 1 number
|
||||
func ValidatePasswordRequirements(password string) error {
|
||||
|
|
@ -332,11 +341,48 @@ func ValidatePasswordRequirements(password string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if len(password) >= 7 &&
|
||||
if len(password) >= 12 &&
|
||||
number &&
|
||||
symbol {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("Password does not meet validation requirements")
|
||||
return errors.New("Password does not meet required criteria")
|
||||
}
|
||||
|
||||
// SetFakePassword sets a stand-in password consisting of random text generated by filling in keySize bytes with
|
||||
// random data and then base64 encoding those bytes.
|
||||
//
|
||||
// Usage should be limited to cases such as SSO users where a stand-in password is needed to satisfy `NOT NULL` constraints.
|
||||
// There is no guarantee that the generated password will otherwise satisfy complexity, length or
|
||||
// other requirements of standard password validation.
|
||||
func (u *User) SetFakePassword(keySize, cost int) error {
|
||||
plaintext, err := server.GenerateRandomText(14)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashed, salt, err := saltAndHashPassword(keySize, plaintext, cost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hashed
|
||||
u.Salt = salt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saltAndHashPassword(keySize int, plaintext string, cost int) (hashed []byte, salt string, err error) {
|
||||
salt, err = server.GenerateRandomText(keySize)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
withSalt := []byte(fmt.Sprintf("%s%s", plaintext, salt))
|
||||
hashed, err = bcrypt.GenerateFromPassword(withSalt, cost)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return hashed, salt, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestValidatePassword(t *testing.T) {
|
||||
|
||||
var passwordTests = []struct {
|
||||
passwordTests := []struct {
|
||||
Password, Email string
|
||||
Admin, PasswordReset bool
|
||||
}{
|
||||
|
|
@ -62,6 +62,10 @@ func TestUserPasswordRequirements(t *testing.T) {
|
|||
},
|
||||
{
|
||||
password: "foobarbaz!3",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
password: "foobarbaz!3!",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -76,3 +80,21 @@ func TestUserPasswordRequirements(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaltAndHashPassword(t *testing.T) {
|
||||
passwordTests := []string{"foobar!!", "bazbing!!"}
|
||||
keySize := 24
|
||||
cost := 10
|
||||
|
||||
for _, pwd := range passwordTests {
|
||||
hashed, salt, err := saltAndHashPassword(keySize, pwd, cost)
|
||||
require.NoError(t, err)
|
||||
|
||||
saltAndPass := []byte(fmt.Sprintf("%s%s", pwd, salt))
|
||||
err = bcrypt.CompareHashAndPassword(hashed, saltAndPass)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(hashed, []byte(fmt.Sprint("invalidpassword", salt)))
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ func (s *integrationTestSuite) TestDoubleUserCreationErrors() {
|
|||
params := fleet.UserPayload{
|
||||
Name: ptr.String("user1"),
|
||||
Email: ptr.String("email@asd.com"),
|
||||
Password: ptr.String("pass"),
|
||||
Password: &test.GoodPassword,
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ func (s *integrationTestSuite) TestUserWithoutRoleErrors() {
|
|||
params := fleet.UserPayload{
|
||||
Name: ptr.String("user1"),
|
||||
Email: ptr.String("email@asd.com"),
|
||||
Password: ptr.String("pass"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
}
|
||||
|
||||
resp := s.Do("POST", "/api/latest/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity)
|
||||
|
|
@ -140,7 +140,7 @@ func (s *integrationTestSuite) TestUserWithWrongRoleErrors() {
|
|||
params := fleet.UserPayload{
|
||||
Name: ptr.String("user1"),
|
||||
Email: ptr.String("email@asd.com"),
|
||||
Password: ptr.String("pass"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
GlobalRole: ptr.String("wrongrole"),
|
||||
}
|
||||
resp := s.Do("POST", "/api/latest/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity)
|
||||
|
|
@ -162,7 +162,7 @@ func (s *integrationTestSuite) TestUserCreationWrongTeamErrors() {
|
|||
params := fleet.UserPayload{
|
||||
Name: ptr.String("user2"),
|
||||
Email: ptr.String("email2@asd.com"),
|
||||
Password: ptr.String("pass"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
Teams: &teams,
|
||||
}
|
||||
resp := s.Do("POST", "/api/latest/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity)
|
||||
|
|
@ -1096,7 +1096,7 @@ func (s *integrationTestSuite) TestInvites() {
|
|||
var createFromInviteResp createUserResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/users", fleet.UserPayload{
|
||||
Name: ptr.String("Full Name"),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
Email: ptr.String("a@b.c"),
|
||||
InviteToken: ptr.String(validInviteToken),
|
||||
}, http.StatusOK, &createFromInviteResp)
|
||||
|
|
@ -1121,7 +1121,7 @@ func (s *integrationTestSuite) TestInvites() {
|
|||
// create user from never used but deleted invite
|
||||
s.DoJSON("POST", "/api/latest/fleet/users", fleet.UserPayload{
|
||||
Name: ptr.String("Full Name"),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
Email: ptr.String("a@b.c"),
|
||||
InviteToken: ptr.String(deletedInviteToken),
|
||||
}, http.StatusNotFound, &createFromInviteResp)
|
||||
|
|
@ -1158,7 +1158,7 @@ func (s *integrationTestSuite) TestCreateUserFromInviteErrors() {
|
|||
"empty name",
|
||||
fleet.UserPayload{
|
||||
Name: ptr.String(""),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: &test.GoodPassword,
|
||||
Email: ptr.String("a@b.c"),
|
||||
InviteToken: ptr.String(invite.Token),
|
||||
},
|
||||
|
|
@ -1168,7 +1168,7 @@ func (s *integrationTestSuite) TestCreateUserFromInviteErrors() {
|
|||
"empty email",
|
||||
fleet.UserPayload{
|
||||
Name: ptr.String("Name"),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: &test.GoodPassword,
|
||||
Email: ptr.String(""),
|
||||
InviteToken: ptr.String(invite.Token),
|
||||
},
|
||||
|
|
@ -1188,7 +1188,7 @@ func (s *integrationTestSuite) TestCreateUserFromInviteErrors() {
|
|||
"empty token",
|
||||
fleet.UserPayload{
|
||||
Name: ptr.String("Name"),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: &test.GoodPassword,
|
||||
Email: ptr.String("a@b.c"),
|
||||
InviteToken: ptr.String(""),
|
||||
},
|
||||
|
|
@ -1198,7 +1198,7 @@ func (s *integrationTestSuite) TestCreateUserFromInviteErrors() {
|
|||
"invalid token",
|
||||
fleet.UserPayload{
|
||||
Name: ptr.String("Name"),
|
||||
Password: ptr.String("pass1word!"),
|
||||
Password: &test.GoodPassword,
|
||||
Email: ptr.String("a@b.c"),
|
||||
InviteToken: ptr.String("invalid"),
|
||||
},
|
||||
|
|
@ -2533,7 +2533,7 @@ func (s *integrationTestSuite) TestUsers() {
|
|||
|
||||
// create a new user
|
||||
var createResp createUserResponse
|
||||
userRawPwd := "pass"
|
||||
userRawPwd := test.GoodPassword
|
||||
params := fleet.UserPayload{
|
||||
Name: ptr.String("extra"),
|
||||
Email: ptr.String("extra@asd.com"),
|
||||
|
|
@ -2606,7 +2606,7 @@ func (s *integrationTestSuite) TestUsers() {
|
|||
// modify user - email change, password ok
|
||||
params = fleet.UserPayload{
|
||||
Email: ptr.String("extra2@asd.com"),
|
||||
Password: ptr.String("pass"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/users/%d", u.ID), params, http.StatusOK, &modResp)
|
||||
assert.Equal(t, u.ID, modResp.User.ID)
|
||||
|
|
@ -2621,7 +2621,7 @@ func (s *integrationTestSuite) TestUsers() {
|
|||
// perform a required password change as the user themselves
|
||||
s.token = s.getTestToken(u.Email, userRawPwd)
|
||||
var perfPwdResetResp performRequiredPasswordResetResponse
|
||||
newRawPwd := "new_password!"
|
||||
newRawPwd := test.GoodPassword2
|
||||
s.DoJSON("POST", "/api/latest/fleet/perform_required_password_reset", performRequiredPasswordResetRequest{
|
||||
Password: newRawPwd,
|
||||
ID: u.ID,
|
||||
|
|
@ -3825,7 +3825,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesBrowsing() {
|
|||
},
|
||||
},
|
||||
}
|
||||
password := "p4ssw0rd."
|
||||
password := test.GoodPassword
|
||||
require.NoError(t, teamObserver.SetPassword(password, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), teamObserver)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -4446,7 +4446,7 @@ func (s *integrationTestSuite) TestPasswordReset() {
|
|||
|
||||
// create a new user
|
||||
var createResp createUserResponse
|
||||
userRawPwd := "passw0rd!"
|
||||
userRawPwd := test.GoodPassword
|
||||
params := fleet.UserPayload{
|
||||
Name: ptr.String("forgotpwd"),
|
||||
Email: ptr.String("forgotpwd@example.com"),
|
||||
|
|
@ -4478,7 +4478,7 @@ func (s *integrationTestSuite) TestPasswordReset() {
|
|||
})
|
||||
|
||||
// proceed with reset password
|
||||
userNewPwd := "newpassw0rd!"
|
||||
userNewPwd := test.GoodPassword2
|
||||
res = s.DoRawNoAuth("POST", "/api/latest/fleet/reset_password", jsonMustMarshal(t, resetPasswordRequest{PasswordResetToken: token, NewPassword: userNewPwd}), http.StatusOK)
|
||||
res.Body.Close()
|
||||
|
||||
|
|
@ -4601,7 +4601,7 @@ func (s *integrationTestSuite) TestModifyUser() {
|
|||
|
||||
// create a new user
|
||||
var createResp createUserResponse
|
||||
userRawPwd := "passw0rd!"
|
||||
userRawPwd := test.GoodPassword
|
||||
params := fleet.UserPayload{
|
||||
Name: ptr.String("moduser"),
|
||||
Email: ptr.String("moduser@example.com"),
|
||||
|
|
@ -4639,7 +4639,7 @@ func (s *integrationTestSuite) TestModifyUser() {
|
|||
require.Equal(t, u.Email, modResp.User.Email) // new email is pending confirmation, not changed immediately
|
||||
|
||||
// as the user: set new password without providing current one
|
||||
newRawPwd := userRawPwd + "2"
|
||||
newRawPwd := test.GoodPassword2
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/users/%d", u.ID), fleet.UserPayload{
|
||||
NewPassword: ptr.String(newRawPwd),
|
||||
}, http.StatusUnprocessableEntity, &modResp)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
@ -161,7 +162,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamPolicies() {
|
|||
s.token = oldToken
|
||||
})
|
||||
|
||||
password := "garbage"
|
||||
password := test.GoodPassword
|
||||
email := "testteam@user.com"
|
||||
|
||||
u := &fleet.User{
|
||||
|
|
@ -271,7 +272,7 @@ func (s *integrationEnterpriseTestSuite) TestAvailableTeams() {
|
|||
Email: "available@example.com",
|
||||
GlobalRole: ptr.String("observer"),
|
||||
}
|
||||
err = user.SetPassword("foobar123#", 10, 10)
|
||||
err = user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.Nil(t, err)
|
||||
user, err = s.ds.NewUser(context.Background(), user)
|
||||
require.Nil(t, err)
|
||||
|
|
@ -381,7 +382,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamEndpoints() {
|
|||
Email: "user@example.com",
|
||||
GlobalRole: ptr.String("observer"),
|
||||
}
|
||||
require.NoError(t, user.SetPassword("foobar123#", 10, 10))
|
||||
require.NoError(t, user.SetPassword(test.GoodPassword, 10, 10))
|
||||
user, err := s.ds.NewUser(context.Background(), user)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ package service
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
|
|
@ -31,21 +29,11 @@ func (svc *Service) CreateInitialUser(ctx context.Context, p fleet.UserPayload)
|
|||
}
|
||||
|
||||
func (svc *Service) newUser(ctx context.Context, p fleet.UserPayload) (*fleet.User, error) {
|
||||
var ssoEnabled bool
|
||||
// if user is SSO generate a fake password
|
||||
if (p.SSOInvite != nil && *p.SSOInvite) || (p.SSOEnabled != nil && *p.SSOEnabled) {
|
||||
fakePassword, err := server.GenerateRandomText(14)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "generate stand-in password")
|
||||
}
|
||||
p.Password = &fakePassword
|
||||
ssoEnabled = true
|
||||
}
|
||||
user, err := p.User(svc.config.Auth.SaltKeySize, svc.config.Auth.BcryptCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.SSOEnabled = ssoEnabled
|
||||
|
||||
user, err = svc.ds.NewUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/service/async"
|
||||
"github.com/fleetdm/fleet/v4/server/sso"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -130,17 +131,17 @@ var testUsers = map[string]struct {
|
|||
GlobalRole *string
|
||||
}{
|
||||
"admin1": {
|
||||
PlaintextPassword: "foobarbaz1234!",
|
||||
PlaintextPassword: test.GoodPassword,
|
||||
Email: "admin1@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||
},
|
||||
"user1": {
|
||||
PlaintextPassword: "foobarbaz1234!",
|
||||
PlaintextPassword: test.GoodPassword,
|
||||
Email: "user1@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleMaintainer),
|
||||
},
|
||||
"user2": {
|
||||
PlaintextPassword: "bazfoo1234!",
|
||||
PlaintextPassword: test.GoodPassword,
|
||||
Email: "user2@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -809,6 +809,10 @@ func (svc *Service) PerformRequiredPasswordReset(ctx context.Context, password s
|
|||
return nil, fleet.NewInvalidArgumentError("new_password", "cannot reuse old password")
|
||||
}
|
||||
|
||||
if err := fleet.ValidatePasswordRequirements(password); err != nil {
|
||||
return nil, fleet.NewInvalidArgumentError("new_password", "Password does not meet required criteria")
|
||||
}
|
||||
|
||||
user.AdminForcedPasswordReset = false
|
||||
err := svc.setNewPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
|
|
@ -895,9 +899,10 @@ func (svc *Service) ResetPassword(ctx context.Context, token, password string) e
|
|||
return fleet.NewInvalidArgumentError("new_password", "cannot reuse old password")
|
||||
}
|
||||
|
||||
// password requirements are validated as part of `setNewPassword``
|
||||
err = svc.setNewPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "setting new password")
|
||||
return fleet.NewInvalidArgumentError("new_password", err.Error())
|
||||
}
|
||||
|
||||
// delete password reset tokens for user
|
||||
|
|
|
|||
|
|
@ -302,14 +302,15 @@ func TestUserAuth(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
||||
|
||||
tt.user.SetPassword("p4ssw0rd.", 10, 10)
|
||||
err := tt.user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
|
||||
// To test a user reading/modifying itself.
|
||||
u := *tt.user
|
||||
self = &u
|
||||
|
||||
// A user can always read itself (read rego action).
|
||||
_, err := svc.User(ctx, tt.user.ID)
|
||||
_, err = svc.User(ctx, tt.user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// A user can always write itself (write rego action).
|
||||
|
|
@ -317,7 +318,7 @@ func TestUserAuth(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// A user can always change its own password (change_password rego action).
|
||||
_, err = svc.ModifyUser(ctx, tt.user.ID, fleet.UserPayload{Password: ptr.String("p4ssw0rd."), NewPassword: ptr.String("p4ssw0rd.3")})
|
||||
_, err = svc.ModifyUser(ctx, tt.user.ID, fleet.UserPayload{Password: ptr.String(test.GoodPassword), NewPassword: ptr.String(test.GoodPassword2)})
|
||||
require.NoError(t, err)
|
||||
|
||||
changeRole := func(role string) string {
|
||||
|
|
@ -353,7 +354,7 @@ func TestUserAuth(t *testing.T) {
|
|||
_, err = svc.CreateUser(ctx, fleet.UserPayload{
|
||||
Name: ptr.String("Some Name"),
|
||||
Email: ptr.String("some@email.com"),
|
||||
Password: ptr.String("passw0rd."),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
Teams: &teams,
|
||||
})
|
||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||
|
|
@ -361,7 +362,7 @@ func TestUserAuth(t *testing.T) {
|
|||
_, err = svc.CreateUser(ctx, fleet.UserPayload{
|
||||
Name: ptr.String("Some Name"),
|
||||
Email: ptr.String("some@email.com"),
|
||||
Password: ptr.String("passw0rd."),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||
})
|
||||
checkAuthErr(t, tt.shouldFailGlobalWrite, err)
|
||||
|
|
@ -403,10 +404,10 @@ func TestUserAuth(t *testing.T) {
|
|||
_, err = svc.RequirePasswordReset(ctx, userTeamMaintainerID, false)
|
||||
checkAuthErr(t, tt.shouldFailTeamPasswordReset, err)
|
||||
|
||||
_, err = svc.ModifyUser(ctx, userGlobalMaintainerID, fleet.UserPayload{NewPassword: ptr.String("passw0rd.2")})
|
||||
_, err = svc.ModifyUser(ctx, userGlobalMaintainerID, fleet.UserPayload{NewPassword: ptr.String(test.GoodPassword2)})
|
||||
checkAuthErr(t, tt.shouldFailGlobalChangePassword, err)
|
||||
|
||||
_, err = svc.ModifyUser(ctx, userTeamMaintainerID, fleet.UserPayload{NewPassword: ptr.String("passw0rd.2")})
|
||||
_, err = svc.ModifyUser(ctx, userTeamMaintainerID, fleet.UserPayload{NewPassword: ptr.String(test.GoodPassword2)})
|
||||
checkAuthErr(t, tt.shouldFailTeamChangePassword, err)
|
||||
|
||||
_, err = svc.ListUsers(ctx, fleet.UserListOptions{})
|
||||
|
|
@ -423,7 +424,8 @@ func TestModifyUserEmail(t *testing.T) {
|
|||
ID: 3,
|
||||
Email: "foo@bar.com",
|
||||
}
|
||||
user.SetPassword("password", 10, 10)
|
||||
err := user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
ms := new(mock.Store)
|
||||
ms.PendingEmailChangeFunc = func(ctx context.Context, id uint, em, tk string) error {
|
||||
return nil
|
||||
|
|
@ -461,10 +463,10 @@ func TestModifyUserEmail(t *testing.T) {
|
|||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: user})
|
||||
payload := fleet.UserPayload{
|
||||
Email: ptr.String("zip@zap.com"),
|
||||
Password: ptr.String("password"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
Position: ptr.String("minion"),
|
||||
}
|
||||
_, err := svc.ModifyUser(ctx, 3, payload)
|
||||
_, err = svc.ModifyUser(ctx, 3, payload)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, ms.PendingEmailChangeFuncInvoked)
|
||||
assert.True(t, ms.SaveUserFuncInvoked)
|
||||
|
|
@ -475,7 +477,8 @@ func TestModifyUserEmailNoPassword(t *testing.T) {
|
|||
ID: 3,
|
||||
Email: "foo@bar.com",
|
||||
}
|
||||
user.SetPassword("password", 10, 10)
|
||||
err := user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
ms := new(mock.Store)
|
||||
ms.PendingEmailChangeFunc = func(ctx context.Context, id uint, em, tk string) error {
|
||||
return nil
|
||||
|
|
@ -483,6 +486,9 @@ func TestModifyUserEmailNoPassword(t *testing.T) {
|
|||
ms.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
ms.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
config := &fleet.AppConfig{
|
||||
SMTPSettings: fleet.SMTPSettings{
|
||||
|
|
@ -504,9 +510,8 @@ func TestModifyUserEmailNoPassword(t *testing.T) {
|
|||
payload := fleet.UserPayload{
|
||||
Email: ptr.String("zip@zap.com"),
|
||||
// NO PASSWORD
|
||||
// Password: ptr.String("password"),
|
||||
}
|
||||
_, err := svc.ModifyUser(ctx, 3, payload)
|
||||
_, err = svc.ModifyUser(ctx, 3, payload)
|
||||
require.NotNil(t, err)
|
||||
var iae *fleet.InvalidArgumentError
|
||||
ok := errors.As(err, &iae)
|
||||
|
|
@ -521,7 +526,8 @@ func TestModifyAdminUserEmailNoPassword(t *testing.T) {
|
|||
ID: 3,
|
||||
Email: "foo@bar.com",
|
||||
}
|
||||
user.SetPassword("password", 10, 10)
|
||||
err := user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
ms := new(mock.Store)
|
||||
ms.PendingEmailChangeFunc = func(ctx context.Context, id uint, em, tk string) error {
|
||||
return nil
|
||||
|
|
@ -529,6 +535,9 @@ func TestModifyAdminUserEmailNoPassword(t *testing.T) {
|
|||
ms.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
ms.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
config := &fleet.AppConfig{
|
||||
SMTPSettings: fleet.SMTPSettings{
|
||||
|
|
@ -550,9 +559,9 @@ func TestModifyAdminUserEmailNoPassword(t *testing.T) {
|
|||
payload := fleet.UserPayload{
|
||||
Email: ptr.String("zip@zap.com"),
|
||||
// NO PASSWORD
|
||||
// Password: ptr.String("password"),
|
||||
// Password: &test.TestGoodPassword,
|
||||
}
|
||||
_, err := svc.ModifyUser(ctx, 3, payload)
|
||||
_, err = svc.ModifyUser(ctx, 3, payload)
|
||||
require.NotNil(t, err)
|
||||
var iae *fleet.InvalidArgumentError
|
||||
ok := errors.As(err, &iae)
|
||||
|
|
@ -567,7 +576,8 @@ func TestModifyAdminUserEmailPassword(t *testing.T) {
|
|||
ID: 3,
|
||||
Email: "foo@bar.com",
|
||||
}
|
||||
user.SetPassword("password", 10, 10)
|
||||
err := user.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
ms := new(mock.Store)
|
||||
ms.PendingEmailChangeFunc = func(ctx context.Context, id uint, em, tk string) error {
|
||||
return nil
|
||||
|
|
@ -601,9 +611,9 @@ func TestModifyAdminUserEmailPassword(t *testing.T) {
|
|||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: user})
|
||||
payload := fleet.UserPayload{
|
||||
Email: ptr.String("zip@zap.com"),
|
||||
Password: ptr.String("password"),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
}
|
||||
_, err := svc.ModifyUser(ctx, 3, payload)
|
||||
_, err = svc.ModifyUser(ctx, 3, payload)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, ms.PendingEmailChangeFuncInvoked)
|
||||
assert.True(t, ms.SaveUserFuncInvoked)
|
||||
|
|
@ -639,7 +649,7 @@ func testUsersCreateUserForcePasswdReset(t *testing.T, ds *mysql.Datastore) {
|
|||
Email: "admin@foo.com",
|
||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||
}
|
||||
err := admin.SetPassword("p4ssw0rd.", 10, 10)
|
||||
err := admin.SetPassword(test.GoodPassword, 10, 10)
|
||||
require.NoError(t, err)
|
||||
admin, err = ds.NewUser(context.Background(), admin)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -649,7 +659,7 @@ func testUsersCreateUserForcePasswdReset(t *testing.T, ds *mysql.Datastore) {
|
|||
user, err := svc.CreateUser(ctx, fleet.UserPayload{
|
||||
Name: ptr.String("Some Observer"),
|
||||
Email: ptr.String("some-observer@email.com"),
|
||||
Password: ptr.String("passw0rd."),
|
||||
Password: ptr.String(test.GoodPassword),
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -671,29 +681,29 @@ func testUsersChangePassword(t *testing.T, ds *mysql.Datastore) {
|
|||
}{
|
||||
{ // all good
|
||||
user: users["admin1@example.com"],
|
||||
oldPassword: "foobarbaz1234!",
|
||||
newPassword: "12345cat!",
|
||||
oldPassword: test.GoodPassword,
|
||||
newPassword: test.GoodPassword2,
|
||||
},
|
||||
{ // prevent password reuse
|
||||
user: users["admin1@example.com"],
|
||||
oldPassword: "12345cat!",
|
||||
newPassword: "foobarbaz1234!",
|
||||
oldPassword: test.GoodPassword2,
|
||||
newPassword: test.GoodPassword,
|
||||
wantErr: fleet.NewInvalidArgumentError("new_password", "cannot reuse old password"),
|
||||
},
|
||||
{ // all good
|
||||
user: users["user1@example.com"],
|
||||
oldPassword: "foobarbaz1234!",
|
||||
newPassword: "newpassa1234!",
|
||||
oldPassword: test.GoodPassword,
|
||||
newPassword: test.GoodPassword2,
|
||||
},
|
||||
{ // bad old password
|
||||
user: users["user1@example.com"],
|
||||
oldPassword: "wrong_password",
|
||||
newPassword: "12345cat!",
|
||||
newPassword: test.GoodPassword2,
|
||||
anyErr: true,
|
||||
},
|
||||
{ // missing old password
|
||||
user: users["user1@example.com"],
|
||||
newPassword: "123cataaa!",
|
||||
newPassword: test.GoodPassword2,
|
||||
wantErr: fleet.NewInvalidArgumentError("old_password", "Old password cannot be empty"),
|
||||
},
|
||||
}
|
||||
|
|
@ -790,7 +800,7 @@ func TestPerformRequiredPasswordReset(t *testing.T) {
|
|||
_, err = svc.RequirePasswordReset(ctx, user.ID, false)
|
||||
require.Nil(t, err)
|
||||
ctx = refreshCtx(t, ctx, user, ds, session)
|
||||
_, err = svc.PerformRequiredPasswordReset(ctx, "new_pass")
|
||||
_, err = svc.PerformRequiredPasswordReset(ctx, test.GoodPassword2)
|
||||
require.NotNil(t, err)
|
||||
|
||||
_, err = svc.RequirePasswordReset(ctx, user.ID, true)
|
||||
|
|
@ -802,14 +812,14 @@ func TestPerformRequiredPasswordReset(t *testing.T) {
|
|||
require.Equal(t, "validation failed: new_password cannot reuse old password", err.Error())
|
||||
|
||||
// should succeed with good new password
|
||||
u, err := svc.PerformRequiredPasswordReset(ctx, "new_pass")
|
||||
u, err := svc.PerformRequiredPasswordReset(ctx, test.GoodPassword2)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, u.AdminForcedPasswordReset)
|
||||
|
||||
ctx = context.Background()
|
||||
|
||||
// Now user should be able to login with new password
|
||||
u, _, err = svc.Login(ctx, tt.Email, "new_pass")
|
||||
u, _, err = svc.Login(ctx, tt.Email, test.GoodPassword2)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, u.AdminForcedPasswordReset)
|
||||
})
|
||||
|
|
@ -828,20 +838,20 @@ func TestResetPassword(t *testing.T) {
|
|||
}{
|
||||
{ // all good
|
||||
token: "abcd",
|
||||
newPassword: "123cat!",
|
||||
newPassword: test.GoodPassword2,
|
||||
},
|
||||
{ // prevent reuse
|
||||
token: "abcd",
|
||||
newPassword: "123cat!",
|
||||
newPassword: test.GoodPassword2,
|
||||
wantErr: fleet.NewInvalidArgumentError("new_password", "cannot reuse old password"),
|
||||
},
|
||||
{ // bad token
|
||||
token: "dcbaz",
|
||||
newPassword: "123cat!",
|
||||
newPassword: test.GoodPassword,
|
||||
wantErr: sql.ErrNoRows,
|
||||
},
|
||||
{ // missing token
|
||||
newPassword: "123cat!",
|
||||
newPassword: test.GoodPassword,
|
||||
wantErr: fleet.NewInvalidArgumentError("token", "Token cannot be empty field"),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
UserNoRoles = &fleet.User{
|
||||
GoodPassword = "password123#"
|
||||
GoodPassword2 = "password123!"
|
||||
UserNoRoles = &fleet.User{
|
||||
ID: 1,
|
||||
}
|
||||
UserAdmin = &fleet.User{
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ create_user_endpoint="api/latest/fleet/users/admin"
|
|||
data='{
|
||||
"name": "Andre Verot",
|
||||
"email": "andre@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -48,7 +48,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Joanne Jackson",
|
||||
"email": "jo@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -64,7 +64,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Cheryl Gardner",
|
||||
"email": "cheryl87@domain.tld",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -80,7 +80,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Lisa Walsh",
|
||||
"email": "lisa_walsh@domain.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -104,7 +104,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Christopher Mitchell",
|
||||
"email": "christopher98@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "admin"
|
||||
}'
|
||||
|
|
@ -114,7 +114,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Kai Boucher",
|
||||
"email": "boucher_@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null
|
||||
}'
|
||||
|
|
@ -124,7 +124,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Henry Lewis",
|
||||
"email": "henry.lewis@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -140,7 +140,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Shintaro Sato",
|
||||
"email": "shin-sato@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -160,7 +160,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Rosie Thomas",
|
||||
"email": "rosie@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "maintainer"
|
||||
}'
|
||||
|
|
@ -170,7 +170,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Pat Moreno",
|
||||
"email": "pat-moreno@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"teams": [
|
||||
|
|
@ -186,7 +186,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Mohammad Patel",
|
||||
"email": "mo-patel@thecompany.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "observer"
|
||||
}'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ create_user_endpoint="api/latest/fleet/users/admin"
|
|||
data='{
|
||||
"name": "Anna",
|
||||
"email": "anna@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "admin",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
@ -22,7 +22,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Mary",
|
||||
"email": "mary@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "maintainer",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
@ -33,7 +33,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Oliver",
|
||||
"email": "oliver@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "observer",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ create_user_endpoint="api/latest/fleet/users/admin"
|
|||
data='{
|
||||
"name": "Anna",
|
||||
"email": "anna@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "admin",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
@ -37,7 +37,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Mary",
|
||||
"email": "mary@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "maintainer",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
@ -48,7 +48,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Oliver",
|
||||
"email": "oliver@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": "observer",
|
||||
"admin_forced_password_reset": false
|
||||
|
|
@ -59,7 +59,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Marco",
|
||||
"email": "marco@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"admin_forced_password_reset": false,
|
||||
|
|
@ -80,7 +80,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Anita T. Admin",
|
||||
"email": "anita@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"admin_forced_password_reset": false,
|
||||
|
|
@ -97,7 +97,7 @@ curl -X POST $CURL_FLAGS -H "Authorization: Bearer $TOKEN" "$SERVER_URL/$create_
|
|||
data='{
|
||||
"name": "Toni",
|
||||
"email": "toni@organization.com",
|
||||
"password": "user123#",
|
||||
"password": "password123#",
|
||||
"invited_by": 1,
|
||||
"global_role": null,
|
||||
"admin_forced_password_reset": false,
|
||||
|
|
|
|||
2
website/views/pages/get-started.ejs
vendored
2
website/views/pages/get-started.ejs
vendored
|
|
@ -50,7 +50,7 @@
|
|||
<p>The Fleet UI is now available at <a href="http://localhost:1337"
|
||||
target="_blank">http://localhost:1337</a>. Use the credentials below to login.</p>
|
||||
<p class="mb-2"><strong>Email:</strong> admin@example.com</p>
|
||||
<p><strong>Password:</strong> admin123#</p>
|
||||
<p><strong>Password:</strong> preview1337#</p>
|
||||
</div>
|
||||
<div style="padding-top: 60px;">
|
||||
<h2 class="mb-4">Next steps</h2>
|
||||
|
|
|
|||
Loading…
Reference in a new issue