Skip to content

Commit

Permalink
feat: allow disable login on ui (verdaccio#2228)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanpicado authored May 5, 2021
1 parent 393125b commit 0da7031
Show file tree
Hide file tree
Showing 20 changed files with 168 additions and 67 deletions.
15 changes: 15 additions & 0 deletions .changeset/afraid-mice-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@verdaccio/types': minor
'@verdaccio/ui-theme': minor
'@verdaccio/web': minor
---

allow disable login on ui and endpoints

To be able disable the login, set `login: false`, anything else would enable login. This flag will disable access via UI and web endpoints.

```yml
web:
title: verdaccio
login: false
```
1 change: 1 addition & 0 deletions packages/core/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare module '@verdaccio/types' {
darkMode?: boolean;
url_prefix?: string;
language?: string;
login?: boolean;
scope?: string;
pkgManagers?: PackageManagers[];
};
Expand Down
25 changes: 22 additions & 3 deletions packages/plugins/ui-theme/src/App/Header/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
render,
fireEvent,
waitFor,
screen,
waitForElementToBeRemoved,
} from 'verdaccio-ui/utils/test-react-testing-library';

Expand All @@ -23,16 +24,17 @@ const props = {
/* eslint-disable react/jsx-no-bind*/
describe('<Header /> component with logged in state', () => {
test('should load the component in logged out state', () => {
const { queryByTestId, getByText } = render(
render(
<Router>
<AppContextProvider>
<Header />
</AppContextProvider>
</Router>
);

expect(queryByTestId('header--menu-accountcircle')).toBeNull();
expect(getByText('Login')).toBeTruthy();
expect(screen.queryByTestId('header--menu-accountcircle')).toBeNull();
expect(screen.getByText('Login')).toBeTruthy();
expect(screen.queryByTestId('header--button-login')).toBeInTheDocument();
});

test('should load the component in logged in state', () => {
Expand Down Expand Up @@ -135,5 +137,22 @@ describe('<Header /> component with logged in state', () => {
expect(hasRegistrationInfoModalBeenRemoved).not.toBeDefined();
});

test('should hide login if is disabled', () => {
// @ts-expect-error
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
base: 'foo',
login: false,
};
render(
<Router>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);

expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
});

test.todo('autocompletion should display suggestions according to the type value');
});
1 change: 1 addition & 0 deletions packages/plugins/ui-theme/src/App/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
<InnerNavBar>
<HeaderLeft />
<HeaderRight
hasLogin={configOptions?.login}
onLogout={handleLogout}
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
Expand Down
33 changes: 20 additions & 13 deletions packages/plugins/ui-theme/src/App/Header/HeaderRight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { RightSide } from './styles';
interface Props {
withoutSearch?: boolean;
username?: string | null;
hasLogin?: boolean;
onToggleLogin: () => void;
onOpenRegistryInfoDialog: () => void;
onToggleMobileNav: () => void;
Expand All @@ -22,13 +23,15 @@ const HeaderRight: React.FC<Props> = ({
withoutSearch = false,
username,
onToggleLogin,
hasLogin,
onLogout,
onToggleMobileNav,
onOpenRegistryInfoDialog,
}) => {
const themeContext = useContext(ThemeContext);
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const hideLoginSection = hasLogin === false;

const { t } = useTranslation();

Expand Down Expand Up @@ -90,19 +93,23 @@ const HeaderRight: React.FC<Props> = ({
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
/>

{username ? (
<HeaderMenu
anchorEl={anchorEl}
isMenuOpen={isMenuOpen}
onLoggedInMenu={handleLoggedInMenu}
onLoggedInMenuClose={handleLoggedInMenuClose}
onLogout={onLogout}
username={username}
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{t('button.login')}
</Button>
{!hideLoginSection && (
<>
{username ? (
<HeaderMenu
anchorEl={anchorEl}
isMenuOpen={isMenuOpen}
onLoggedInMenu={handleLoggedInMenu}
onLoggedInMenuClose={handleLoggedInMenuClose}
onLogout={onLogout}
username={username}
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{t('button.login')}
</Button>
)}
</>
)}
</RightSide>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const defaultValues: ConfigProviderProps = {
pkgManagers: ['yarn', 'pnpm', 'npm'],
scope: '',
base: '',
login: true,
url_prefix: '',
title: 'Verdaccio',
},
Expand Down Expand Up @@ -46,7 +47,6 @@ const AppConfigurationProvider: FunctionComponent = ({ children }) => {
[configOptions]
);

// @ts-ignore
return (
<AppConfigurationContext.Provider value={value}>{children}</AppConfigurationContext.Provider>
);
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/ui-theme/tools/_verdaccio.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ web:
title: Verdaccio Local Dev
sort_packages: asc
primary_color: #CCC
login: true
pkgManagers:
- npm
- yarn
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/ui-theme/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface VerdaccioOptions {
primaryColor: string;
darkMode: boolean;
uri?: string;
login?: boolean;
language?: string;
version?: string;
protocol?: string;
Expand Down
11 changes: 10 additions & 1 deletion packages/web/src/api/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,20 @@ function addPackageWebApi(
auth: IAuth,
config: Config
): void {
const isLoginEnabled = config?.web?.login === true ?? true;
const anonymousRemoteUser: RemoteUser = {
name: undefined,
real_groups: [],
groups: [],
};

debug('initialized package web api');
const checkAllow = (name: string, remoteUser: RemoteUser): Promise<boolean> =>
new Promise((resolve, reject): void => {
debug('is login disabled %o', isLoginEnabled);
const remoteUserAccess = !isLoginEnabled ? anonymousRemoteUser : remoteUser;
try {
auth.allow_access({ packageName: name }, remoteUser, (err, allowed): void => {
auth.allow_access({ packageName: name }, remoteUserAccess, (err, allowed): void => {
if (err) {
resolve(false);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/api/readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function addReadmeWebApi(route: Router, storage: IStorageHandler, auth: IAuth):
uplinksLook: true,
req,
callback: function (err, info): void {
debug('readme plg %o', info);
debug('readme plg %o', info?.name);
if (err) {
return next(err);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/web/src/middleware/web-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import addPackageWebApi from '../api/package';
import addUserAuthApi from '../api/user';
import addReadmeWebApi from '../api/readme';
import addSidebarWebApi from '../api/sidebar';
import { hasLogin } from '../utils/web-utils';
import { setSecurityWebHeaders } from './security';

export function webAPI(config: Config, auth: IAuth, storage: IStorageHandler): Router {
Expand All @@ -33,7 +34,9 @@ export function webAPI(config: Config, auth: IAuth, storage: IStorageHandler): R
addReadmeWebApi(route, storage, auth);
addSidebarWebApi(route, config, storage, auth);
addSearchWebApi(route, storage, auth);
addUserAuthApi(route, auth, config);
if (hasLogin(config)) {
addUserAuthApi(route, auth, config);
}
// What are you looking for? logout? client side will remove token when user click logout,
// or it will auto expire after 24 hours.
// This token is different with the token send to npm client.
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/renderHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { HEADERS } from '@verdaccio/commons-api';
import { getPublicUrl } from '@verdaccio/url';

import { WEB_TITLE } from '@verdaccio/config';
import { validatePrimaryColor } from './utils/web-utils';
import { hasLogin, validatePrimaryColor } from './utils/web-utils';
import renderTemplate from './template';

const pkgJSON = require('../package.json');
Expand All @@ -26,6 +26,7 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
const language = config?.i18n?.web ?? DEFAULT_LANGUAGE;
const darkMode = config?.web?.darkMode ?? false;
const title = config?.web?.title ?? WEB_TITLE;
const login = hasLogin(config);
const scope = config?.web?.scope ?? '';
// FIXME: logo URI is incomplete
let logoURI = config?.web?.logo ?? '';
Expand All @@ -49,6 +50,7 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
primaryColor,
version,
logoURI,
login,
pkgManagers,
title,
scope,
Expand Down
6 changes: 5 additions & 1 deletion packages/web/src/utils/web-utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash';
import buildDebug from 'debug';
import { isObject } from '@verdaccio/utils';
import { Package, Author } from '@verdaccio/types';
import { Package, Author, ConfigYaml } from '@verdaccio/types';
import { normalizeContributors } from '@verdaccio/store';

import sanitizyReadme from '@verdaccio/readme';
Expand Down Expand Up @@ -109,3 +109,7 @@ export function sortByName(packages: any[], orderAscending: boolean | void = tru
return orderAscending ? (comparatorNames ? -1 : 1) : comparatorNames ? 1 : -1;
});
}

export function hasLogin(config: ConfigYaml) {
return _.isNil(config?.web?.login) || config?.web?.login === true;
}
13 changes: 13 additions & 0 deletions packages/web/test/api.user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ describe('test web server', () => {
});
});

test('log in should be disabled', async () => {
return supertest(await initializeServer('login-disabled.yaml'))
.post('/-/verdaccio/login')
.send(
JSON.stringify({
username: 'test',
password: 'test',
})
)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
});

test.todo('should change password');
test.todo('should not change password if flag is disabled');
});
33 changes: 33 additions & 0 deletions packages/web/test/config/login-disabled.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
store:
memory:
limit: 1000

auth:
auth-memory:
users:
test:
name: test
password: test

web:
title: verdaccio
login: false

publish:
allow_offline: false

uplinks:

logs: { type: stdout, format: pretty, level: trace }

packages:
'@*/*':
access: $anonymous
publish: $anonymous
'**':
access: $anonymous
publish: $anonymous
_debug: true

flags:
changePassword: true
Loading

0 comments on commit 0da7031

Please sign in to comment.