Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auto_login=off error on login and editing a user + FE tests #3471

Merged
merged 9 commits into from
Aug 21, 2024
Merged
6 changes: 5 additions & 1 deletion src/backend/base/langflow/api/v1/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,21 @@
Update an existing user's data.
"""

update_password = user_update.password != None and user_update.password != ""

Check failure on line 93 in src/backend/base/langflow/api/v1/users.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (E711)

src/backend/base/langflow/api/v1/users.py:93:47: E711 Comparison to `None` should be `cond is not None`

Check failure on line 93 in src/backend/base/langflow/api/v1/users.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (E711)

src/backend/base/langflow/api/v1/users.py:93:47: E711 Comparison to `None` should be `cond is not None`

if not user.is_superuser and user_update.is_superuser:
raise HTTPException(status_code=403, detail="Permission denied")

if not user.is_superuser and user.id != user_id:
raise HTTPException(status_code=403, detail="Permission denied")
if user_update.password:
if update_password:
if not user.is_superuser:
raise HTTPException(status_code=400, detail="You can't change your password here")
user_update.password = get_password_hash(user_update.password)

if user_db := get_user_by_id(session, user_id):
if update_password == False:

Check failure on line 106 in src/backend/base/langflow/api/v1/users.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (E712)

src/backend/base/langflow/api/v1/users.py:106:12: E712 Avoid equality comparisons to `False`; use `if not update_password:` for false checks

Check failure on line 106 in src/backend/base/langflow/api/v1/users.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (E712)

src/backend/base/langflow/api/v1/users.py:106:12: E712 Avoid equality comparisons to `False`; use `if not update_password:` for false checks
user_update.password = user_db.password
return update_user(user_db, user_update, session)
else:
raise HTTPException(status_code=404, detail="User not found")
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,7 @@ export const TABS_ORDER = [
export const LANGFLOW_ACCESS_TOKEN = "access_token_lf";
export const LANGFLOW_API_TOKEN = "apikey_tkn_lflw";
export const LANGFLOW_AUTO_LOGIN_OPTION = "auto_login_lf";
export const LANGFLOW_REFRESH_TOKEN = "refresh_token_lf";

export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS = 60 * 60 - 60 * 60 * 0.1;
export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV =
Expand Down
10 changes: 9 additions & 1 deletion src/frontend/src/contexts/authContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
LANGFLOW_ACCESS_TOKEN,
LANGFLOW_API_TOKEN,
LANGFLOW_AUTO_LOGIN_OPTION,
LANGFLOW_REFRESH_TOKEN,
} from "@/constants/constants";
import { useGetUserData } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
Expand Down Expand Up @@ -76,8 +77,15 @@ export function AuthProvider({ children }): React.ReactElement {
);
}

function login(newAccessToken: string, autoLogin: string) {
function login(
newAccessToken: string,
autoLogin: string,
refreshToken?: string,
) {
cookies.set(LANGFLOW_AUTO_LOGIN_OPTION, autoLogin, { path: "/" });
if (refreshToken) {
cookies.set(LANGFLOW_REFRESH_TOKEN, refreshToken, { path: "/" });
}
setAccessToken(newAccessToken);
setIsAuthenticated(true);
getUser();
Expand Down
21 changes: 15 additions & 6 deletions src/frontend/src/modals/userManagementModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,30 @@ export default function UserManagementModal({
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
console.log(data);

useEffect(() => {
if (!data) {
resetForm();
} else {
handleInput({ target: { name: "username", value: username } });
handleInput({ target: { name: "is_active", value: isActive } });
handleInput({ target: { name: "is_superuser", value: isSuperUser } });
if (open) {
if (!data) {
resetForm();
} else {
setUserName(data.username);
setIsActive(data.is_active);
setIsSuperUser(data.is_superuser);

handleInput({ target: { name: "username", value: username } });
handleInput({ target: { name: "is_active", value: isActive } });
handleInput({ target: { name: "is_superuser", value: isSuperUser } });
}
}
}, [open]);

function resetForm() {
setPassword("");
setUserName("");
setConfirmPassword("");
setIsActive(false);
setIsSuperUser(false);
}

return (
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/pages/AdminPage/LoginPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function LoginAdminPage() {
setSelectedFolder(null);

setLoading(true);
login(res.access_token, "login");
login(res.access_token, "login", res.refresh_token);
},
onError: (error) => {
setErrorData({
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/pages/LoginPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function LoginPage(): JSX.Element {
onSuccess: (data) => {
setSelectedFolder(null);

login(data.access_token, "login");
login(data.access_token, "login", data.refresh_token);
},
onError: (error) => {
setErrorData({
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/types/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export type signUpInputStateType = {

export type inputHandlerEventType = {
target: {
value: string;
value: string | boolean;
name: string;
};
};
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/src/types/contexts/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Users } from "../api";

export type AuthContextType = {
accessToken: string | null;
login: (accessToken: string, autoLogin: string) => void;
login: (
accessToken: string,
autoLogin: string,
refreshToken?: string,
) => void;
userData: Users | null;
setUserData: (userData: Users | null) => void;
authenticationErrorCount: number;
Expand Down
251 changes: 251 additions & 0 deletions src/frontend/tests/end-to-end/auto-login-off.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import { expect, test } from "@playwright/test";

test("user should be able to login on auto_login false, CRUD user's and should see just your own flows", async ({
page,
}) => {
// Intercept the request to any base URL ending with /api/v1/config
await page.route("**/api/v1/auto_login", async (route) => {
const response = await route.fetch();
const responseBody = await response.json();
responseBody.detail.auto_login = false;
route.fulfill({
response,
body: JSON.stringify(responseBody),
headers: {
...response.headers(),
"content-type": "application/json",
},
});
});

const randomName = Math.random().toString(36).substring(5);
const randomPassword = Math.random().toString(36).substring(5);
const secondRandomName = Math.random().toString(36).substring(5);
const randomFlowName = Math.random().toString(36).substring(5);
const secondRandomFlowName = Math.random().toString(36).substring(5);

await page.goto("/");

await page.waitForSelector("text=sign in to langflow", { timeout: 30000 });

await page.getByPlaceholder("Username").fill("langflow");
await page.getByPlaceholder("Password").fill("langflow");

await page.getByRole("button", { name: "Sign In" }).click();

await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

await page.getByTestId("user-profile-settings").click();

await page.getByText("Admin Page", { exact: true }).click();

await page.getByText("New User", { exact: true }).click();

await page.getByPlaceholder("Username").last().fill(randomName);
await page.locator('input[name="password"]').fill(randomPassword);
await page.locator('input[name="confirmpassword"]').fill(randomPassword);

await page.waitForTimeout(1000);

await page.locator("#is_active").click();

await page.getByText("Save", { exact: true }).click();

await page.waitForSelector("text=new user added", { timeout: 30000 });

expect(await page.getByText(randomName, { exact: true }).isVisible()).toBe(
true,
);

await page.getByTestId("icon-Trash2").last().click();
await page.getByText("Delete", { exact: true }).last().click();

await page.waitForSelector("text=user deleted", { timeout: 30000 });

expect(await page.getByText(randomName, { exact: true }).isVisible()).toBe(
false,
);

await page.getByText("New User", { exact: true }).click();

await page.getByPlaceholder("Username").last().fill(randomName);
await page.locator('input[name="password"]').fill(randomPassword);
await page.locator('input[name="confirmpassword"]').fill(randomPassword);

await page.waitForTimeout(1000);

await page.locator("#is_active").click();

await page.getByText("Save", { exact: true }).click();

await page.waitForSelector("text=new user added", { timeout: 30000 });

await page.getByPlaceholder("Username").last().fill(randomName);

await page.getByTestId("icon-Pencil").last().click();

await page.getByPlaceholder("Username").last().fill(secondRandomName);

await page.getByText("Save", { exact: true }).click();

await page.waitForSelector("text=user edited", { timeout: 30000 });

await page.waitForTimeout(1000);

expect(
await page.getByText(secondRandomName, { exact: true }).isVisible(),
).toBe(true);

await page.getByText("My Collection", { exact: true }).last().click();

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}

while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}

await page.getByRole("heading", { name: "Basic Prompting" }).click();

await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});

await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();

await page.getByTestId("flow-configuration-button").click();
await page.getByText("Settings", { exact: true }).last().click();

await page.getByPlaceholder("Flow Name").fill(randomFlowName);

await page.getByText("Save", { exact: true }).click();

await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
timeout: 100000,
});

await page.getByTestId("icon-ChevronLeft").first().click();

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

expect(
await page.getByText(randomFlowName, { exact: true }).last().isVisible(),
).toBe(true);

await page.getByTestId("user-profile-settings").click();

await page.getByText("Log Out", { exact: true }).click();

await page.waitForSelector("text=sign in to langflow", { timeout: 30000 });

await page.getByPlaceholder("Username").fill(secondRandomName);
await page.getByPlaceholder("Password").fill(randomPassword);
await page.waitForTimeout(1000);

await page.getByRole("button", { name: "Sign In" }).click();

await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

expect(
(
await page.waitForSelector("text=this folder is empty", {
timeout: 30000,
})
).isVisible(),
);

while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

await page.getByText("New Project", { exact: true }).click();

await page.getByRole("heading", { name: "Basic Prompting" }).click();

await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});

await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();

await page.getByTestId("flow-configuration-button").click();
await page.getByText("Settings", { exact: true }).last().click();

await page.getByPlaceholder("Flow Name").fill(secondRandomFlowName);

await page.getByText("Save", { exact: true }).click();

await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
timeout: 100000,
});

await page.getByTestId("icon-ChevronLeft").first().click();

expect(
await page.getByText(secondRandomFlowName, { exact: true }).isVisible(),
).toBe(true);
expect(
await page.getByText(randomFlowName, { exact: true }).isVisible(),
).toBe(false);

await page.getByTestId("user-profile-settings").click();

await page.getByText("Log Out", { exact: true }).click();

await page.waitForSelector("text=sign in to langflow", { timeout: 30000 });

await page.getByPlaceholder("Username").fill("langflow");
await page.getByPlaceholder("Password").fill("langflow");

await page.getByRole("button", { name: "Sign In" }).click();

await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});

await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});

expect(
await page.getByText(secondRandomFlowName, { exact: true }).isVisible(),
).toBe(false);
expect(
await page.getByText(randomFlowName, { exact: true }).isVisible(),
).toBe(true);
});
Loading