Skip to content

Commit

Permalink
Experimental login
Browse files Browse the repository at this point in the history
This requires pulpcore >= 3.60
  • Loading branch information
mdellweg committed Jan 5, 2025
1 parent 4015803 commit 906f29c
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 247 deletions.
11 changes: 6 additions & 5 deletions src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export class BaseAPI {

constructor() {
this.http = axios.create({
// adapter + withCredentials ensures no popup on http basic auth fail
adapter: 'fetch',
withCredentials: false,

// baseURL gets set in PulpAPI
// API_BASE_PATH gets set in pulp.ts.
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
// This is what prevents Pulp from sending the "WWW-Authenticat: Basic *" header.
// In turn, firefox will not be asking for a password.
headers: { 'X-Requested-With': 'XMLHttpRequest' },
paramsSerializer: {
serialize: (params) => ParamHelper.getQueryString(params),
},
Expand Down
8 changes: 5 additions & 3 deletions src/api/pulp-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { PulpAPI } from './pulp';
const base = new PulpAPI();

export const PulpLoginAPI = {
try: (username, password) =>
// roles = any api that will always be there and requires auth
base.http.get(`roles/`, { auth: { username, password } }),
get: () => base.http.get('login/'),
// Here is the place to add more authentication methods for the login...
login: (username: string, password: string) =>
base.http.post('login/', {}, { auth: { username, password } }),
logout: () => base.http.delete('login/'),
};
12 changes: 2 additions & 10 deletions src/api/pulp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Cookies from 'js-cookie';
import { config } from 'src/ui-config';
import { BaseAPI } from './base';

Expand All @@ -10,16 +9,9 @@ export class PulpAPI extends BaseAPI {
super();

this.http.interceptors.request.use((request) => {
if (!request.auth) {
request.auth = JSON.parse(
window.sessionStorage.credentials ||
window.localStorage.credentials ||
'{}',
);
}

// This is kind of delayed, because the settings promise may be evaluated later.
// In search for a better solution.
request.baseURL = config.API_BASE_PATH;
request.headers['X-CSRFToken'] = Cookies.get('csrftoken');

return request;
});
Expand Down
26 changes: 14 additions & 12 deletions src/app-context.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { type ReactNode, createContext, useContext, useState } from 'react';
import { type AlertType } from 'src/components';
import { useUserContext } from './user-context';

export interface IAccount {
username?: string;
pulp_href?: string;
prn?: string;
}
export interface IAppContextType {
alerts: AlertType[];
featureFlags; // deprecated
hasPermission: (name: string) => boolean;
queueAlert: (alert: AlertType) => void;
setAlerts: (alerts: AlertType[]) => void;
settings; // deprecated
user; // deprecated
account: IAccount;
}

export const AppContext = createContext<IAppContextType>(undefined);
export const useAppContext = () => useContext(AppContext);

export const AppContextProvider = ({ children }: { children: ReactNode }) => {
export const AppContextProvider = ({
account,
children,
}: {
account: IAccount;
children: ReactNode;
}) => {
const [alerts, setAlerts] = useState<AlertType[]>([]);
const { credentials } = useUserContext();

// hub compat for now
const featureFlags = {
Expand Down Expand Up @@ -49,14 +58,7 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => {
queueAlert,
setAlerts,
settings,
// FIXME: hack
user: credentials
? {
username: credentials.username,
groups: [],
model_permissions: {},
}
: null,
account,
}}
>
{children}
Expand Down
19 changes: 10 additions & 9 deletions src/app-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
FileRepositoryList,
GroupDetail,
GroupList,
LoginPage,
MultiSearch,
MyImports,
MyNamespaces,
Expand All @@ -59,7 +58,7 @@ import {
import { Paths, formatPath } from 'src/paths';
import { config } from 'src/ui-config';
import { loginURL } from 'src/utilities';
import { useUserContext } from './user-context';
import { useAppContext } from './app-context';

interface IRouteConfig {
beta?: boolean;
Expand Down Expand Up @@ -234,11 +233,6 @@ const routes: IRouteConfig[] = [
path: Paths.ansible.namespace.mine,
beta: true,
},
{
component: LoginPage,
path: Paths.meta.login,
noAuth: true,
},
{
component: CollectionDocs,
path: Paths.ansible.collection.docs_page,
Expand Down Expand Up @@ -322,10 +316,12 @@ const AuthHandler = ({
noAuth,
path,
}: IRouteConfig) => {
const { credentials } = useUserContext();
const {
account: { username },
} = useAppContext();
const { pathname } = useLocation();

if (!credentials && !noAuth) {
if (!username && !noAuth) {
// NOTE: also update LoginLink when changing this
if (config.UI_EXTERNAL_LOGIN_URI) {
window.location.replace(loginURL(pathname));
Expand Down Expand Up @@ -401,6 +397,11 @@ export const dataRoutes = [
index: true,
loader: () => redirect(formatPath(Paths.core.status)),
},
{
path: 'login',
id: 'login',
lazy: () => import('src/routes/login').then((m) => convert(m)),
},
...appRoutes(),
// "No matching route" is not handled by the error boundary.
{ path: '*', element: <NotFound /> },
Expand Down
10 changes: 6 additions & 4 deletions src/components/delete-user-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { UserAPI, type UserType } from 'src/api';
import { useAppContext } from 'src/app-context';
import { DeleteModal } from 'src/components';
import { useUserContext } from 'src/user-context';
import { jsxErrorMessage, mapErrorMessages } from 'src/utilities';

interface IProps {
Expand All @@ -20,7 +20,9 @@ export const DeleteUserModal = ({
user,
}: IProps) => {
const [waiting, setWaiting] = useState(false);
const { credentials } = useUserContext();
const {
account: { username },
} = useAppContext();

if (!user || !isOpen) {
return null;
Expand All @@ -30,11 +32,11 @@ export const DeleteUserModal = ({
<DeleteModal
cancelAction={() => closeModal(false)}
deleteAction={() => deleteUser()}
isDisabled={waiting || user.username === credentials.username}
isDisabled={waiting || user.username === username}
spinner={waiting}
title={t`Delete user?`}
>
{user.username === credentials.username ? (
{user.username === username ? (
t`Deleting yourself is not allowed.`
) : (
<Trans>
Expand Down
1 change: 0 additions & 1 deletion src/containers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export { default as FileRepositoryEdit } from './file-repository/edit';
export { default as FileRepositoryList } from './file-repository/list';
export { default as GroupDetail } from './group-management/group-detail';
export { default as GroupList } from './group-management/group-list';
export { default as LoginPage } from './login/login';
export { default as MyImports } from './my-imports/my-imports';
export { default as NamespaceDetail } from './namespace-detail/namespace-detail';
export { default as MyNamespaces } from './namespace-list/my-namespaces';
Expand Down
89 changes: 0 additions & 89 deletions src/containers/login/login.tsx

This file was deleted.

16 changes: 3 additions & 13 deletions src/containers/settings/user-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { t } from '@lingui/core/macro';
import { Button } from '@patternfly/react-core';
import { useEffect, useState } from 'react';
import { UserAPI, type UserType } from 'src/api';
import { useAppContext } from 'src/app-context';
import {
AlertList,
type AlertType,
Expand All @@ -10,7 +11,6 @@ import {
UserFormPage,
closeAlert,
} from 'src/components';
import { useUserContext } from 'src/user-context';
import {
type ErrorMessagesType,
type RouteProps,
Expand All @@ -27,10 +27,8 @@ function UserProfile(_props: RouteProps) {
const [user, setUser] = useState<UserType>();

const {
credentials: { username },
updateUsername,
updatePassword,
} = useUserContext();
account: { username },
} = useAppContext();

const addAlert = (alert: AlertType) => {
setAlerts([...alerts, alert]);
Expand Down Expand Up @@ -65,14 +63,6 @@ function UserProfile(_props: RouteProps) {
variant: 'success',
title: t`Saved changes to user "${username}".`,
});

// update saved credentials when password of logged user is changed
if (user.password) {
updatePassword(user.password);
}
if (username !== user.username) {
updateUsername(user.username);
}
})
.catch((err) => setErrorMessages(mapErrorMessages(err)));

Expand Down
17 changes: 0 additions & 17 deletions src/containers/user-management/user-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
UserFormPage,
} from 'src/components';
import { Paths, formatPath } from 'src/paths';
import { useUserContext } from 'src/user-context';
import {
type ErrorMessagesType,
type RouteProps,
Expand All @@ -19,23 +18,15 @@ import {

function UserEdit(props: RouteProps) {
const [errorMessages, setErrorMessages] = useState<ErrorMessagesType>({});
const [initialState, setInitialState] = useState<UserType>();
const [redirect, setRedirect] = useState<string>();
const [unauthorized, setUnauthorized] = useState<boolean>(false);
const [user, setUser] = useState<UserType>();

const {
credentials: { username },
updateUsername,
updatePassword,
} = useUserContext();

const id = props.routeParams.user_id;
useEffect(() => {
UserAPI.get(id)
.then(({ data: result }) => {
const extendedResult = { ...result, password: '' };
setInitialState({ ...extendedResult });
setUser(extendedResult);
setUnauthorized(false);
})
Expand All @@ -45,14 +36,6 @@ function UserEdit(props: RouteProps) {
const saveUser = () =>
UserAPI.saveUser(user)
.then(() => {
// update saved credentials when password of logged user is changed
if (initialState.username === username && user.password) {
updatePassword(user.password);
}
if (initialState.username === username && username !== user.username) {
updateUsername(user.username);
}

setRedirect(formatPath(Paths.core.user.list));
})
.catch((err) => setErrorMessages(mapErrorMessages(err)));
Expand Down
Loading

0 comments on commit 906f29c

Please sign in to comment.