Skip to content

Commit

Permalink
Optionally disable password accounts (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinejaussoin authored Apr 2, 2022
1 parent 9a4188a commit b16559b
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ [email protected]
SENTRY_URL=
SESSION_SECRET=changeme
DISABLE_ANONYMOUS_LOGIN=false
DISABLE_PASSWORD_LOGIN=false
DISABLE_PASSWORD_REGISTRATION=false
TWITTER_KEY=
TWITTER_SECRET=
GOOGLE_KEY=
Expand Down
2 changes: 2 additions & 0 deletions backend/src/admin/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ router.get('/self-hosting', async (_, res) => {
!!config.SENDGRID_VERIFICATION_EMAIL_TID &&
!!config.SENDGRID_SENDER,
disableAnonymous: config.DISABLE_ANONYMOUS_LOGIN,
disablePasswords: config.DISABLE_PASSWORD_LOGIN,
disablePasswordRegistration: config.DISABLE_PASSWORD_REGISTRATION,
oAuth: {
google: !!config.GOOGLE_KEY && !!config.GOOGLE_SECRET,
github: !!config.GITHUB_KEY && !!config.GITHUB_SECRET,
Expand Down
6 changes: 6 additions & 0 deletions backend/src/auth/passport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ export default () => {
);
} else {
// Regular account login

// Checking if they are allowed in the first place
if (config.DISABLE_PASSWORD_LOGIN) {
return done('Password accounts are disabled', undefined);
}

const identity = await loginUser(username, password);
done(
!identity ? 'User cannot log in' : null,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/common/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface BackendCapabilities {
licenced: boolean;
oAuth: OAuthAvailabilities;
disableAnonymous: boolean;
disablePasswords: boolean;
disablePasswordRegistration: boolean;
}

export interface OAuthAvailabilities {
Expand Down
5 changes: 5 additions & 0 deletions backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ const config: BackendConfig = {
BASE_URL: defaults('BASE_URL', 'http://localhost:80'),
SECURE_COOKIES: defaultsBool('SECURE_COOKIES', false),
DISABLE_ANONYMOUS_LOGIN: defaultsBool('DISABLE_ANONYMOUS_LOGIN', false),
DISABLE_PASSWORD_LOGIN: defaultsBool('DISABLE_PASSWORD_LOGIN', false),
DISABLE_PASSWORD_REGISTRATION: defaultsBool(
'DISABLE_PASSWORD_REGISTRATION',
false
),
TWITTER_KEY: defaults('TWITTER_KEY', ''),
TWITTER_SECRET: defaults('TWITTER_SECRET', ''),
GOOGLE_KEY: defaults('GOOGLE_KEY', ''),
Expand Down
4 changes: 4 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ db().then(() => {
res.status(500).send('You are already logged in');
return;
}
if (config.DISABLE_PASSWORD_REGISTRATION) {
res.status(403).send('Password accounts registration is disabled.');
return;
}
const registerPayload = req.body as RegisterPayload;
if (
(await getIdentityByUsername('password', registerPayload.username)) !==
Expand Down
2 changes: 2 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface BackendConfig {
SECURE_COOKIES: boolean;
SENTRY_URL: string;
DISABLE_ANONYMOUS_LOGIN: boolean;
DISABLE_PASSWORD_LOGIN: boolean;
DISABLE_PASSWORD_REGISTRATION: boolean;
TWITTER_KEY: string;
TWITTER_SECRET: string;
GOOGLE_KEY: string;
Expand Down
37 changes: 37 additions & 0 deletions docs/docs/self-hosting/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
sidebar_position: 5
---

# 🎫 Authentication

When running a self-hosted instance, you can customise how your users will authenticate to the system.

You essentially have 3 authentication mechanisms:

- OAuth (aka Social) authentication (Google, Okta, GitHub etc.)
- Password accounts (the good old email / password combination)
- Anonymous accounts (where the user cannot retrieve his posts and sessions on a different machine)

By default, OAuth is disabled (because it is not configured), and the other two are enabled.



## OAuth

This is a subject of a [dedicated page here](oauth).

## Password accounts

You have two optional settings for password accounts.

You can disable them entirely, by setting the environment variable `DISABLE_PASSWORD_LOGIN` to `true`.

You can also only disable registration, by setting the environment variable `DISABLE_PASSWORD_REGISTRATION` to `true`.

If you want more details on how to set these environment variables, [read this page](optionals).

If you disabled registration, it means the administrator will need to create the accounts manually, by using the [admin screen](admin).

## Anonymous accounts

If you do not wish your users to use anonymous accounts, and force them to authenticate properly, they can be disabled by setting the environment variable `DISABLE_ANONYMOUS_LOGIN` to `true`.
2 changes: 2 additions & 0 deletions docs/docs/self-hosting/optionals.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ services:
BASE_URL: http://localhost:80 # This must be the URL of the frontend app once deployed. Only useful if you need OAuth, SendGrid or Stripe
SECURE_COOKIES: 'false' # You can set this to true if you are using HTTPS. This is more secure.
DISABLE_ANONYMOUS_LOGIN: 'false' # Set to true to disable anonymous accounts
DISABLE_PASSWORD_LOGIN: 'false' # Set to true to disable password accounts (email accounts).
DISABLE_PASSWORD_REGISTRATION: 'false' # Set to true to disable password accounts registration (but not login!)

# -- OAuth: Set these to enable OAuth authentication for one or more provider. This is optional. --
TWITTER_KEY:
Expand Down
128 changes: 82 additions & 46 deletions frontend/src/auth/modal/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ import AnonAuth from './AnonAuth';
import AccountAuth from './AccountAuth';
import useOAuthAvailabilities from '../../global/useOAuthAvailabilities';
import useBackendCapabilities from '../../global/useBackendCapabilities';
import { Alert } from '@mui/material';

interface LoginModalProps {
onClose: () => void;
}

type TabType = 'account' | 'social' | 'anon' | null;

const Login = ({ onClose }: LoginModalProps) => {
const { any } = useOAuthAvailabilities();
const { disableAnonymous } = useBackendCapabilities();
const { disableAnonymous, disablePasswords } = useBackendCapabilities();
const hasNoSocialMediaAuth = !any;
const hasNoWayOfLoggingIn =
hasNoSocialMediaAuth && disableAnonymous && disablePasswords;
const translations = useTranslations();
const fullScreen = useMediaQuery('(max-width:600px)');
const { setUser } = useContext(UserContext);
const [currentTab, setCurrentTab] = useState(
hasNoSocialMediaAuth ? 'account' : 'social'
const [currentTab, setCurrentTab] = useState<TabType>(
getDefaultMode(any, !disablePasswords, !disableAnonymous)
);

const handleClose = useCallback(() => {
Expand All @@ -34,7 +39,7 @@ const Login = ({ onClose }: LoginModalProps) => {
}
}, [onClose]);
const handleTab = useCallback((_: React.ChangeEvent<{}>, value: string) => {
setCurrentTab(value);
setCurrentTab(value as TabType);
}, []);
return (
<Dialog
Expand All @@ -44,50 +49,81 @@ const Login = ({ onClose }: LoginModalProps) => {
open
onClose={handleClose}
>
<AppBar position="static" color="default">
<Tabs
value={currentTab}
onChange={handleTab}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="Login types"
>
{!hasNoSocialMediaAuth ? (
<Tab
label={translations.SocialMediaLogin.header}
value="social"
data-cy="social-tab"
/>
) : null}
<Tab
label={translations.AccountLogin.header}
value="account"
data-cy="account-tab"
/>
{!disableAnonymous ? (
<Tab
label={translations.AnonymousLogin.anonymousAuthHeader}
value="anon"
data-cy="anon-tab"
/>
) : null}
</Tabs>
</AppBar>
<DialogContent>
{currentTab === 'social' ? (
<SocialAuth onClose={onClose} onUser={setUser} />
) : null}
{currentTab === 'account' ? (
<AccountAuth onClose={onClose} onUser={setUser} />
) : null}
{currentTab === 'anon' ? (
<AnonAuth onClose={onClose} onUser={setUser} />
) : null}
</DialogContent>
{hasNoWayOfLoggingIn ? (
<Alert severity="error">
Your administrator disabled all login possibilities (OAuth, password,
anonymous). Ask your administrator to re-enable at least one.
</Alert>
) : (
<>
<AppBar position="static" color="default">
<Tabs
value={currentTab}
onChange={handleTab}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="Login types"
>
{!hasNoSocialMediaAuth ? (
<Tab
label={translations.SocialMediaLogin.header}
value="social"
data-cy="social-tab"
/>
) : null}
{!disablePasswords ? (
<Tab
label={translations.AccountLogin.header}
value="account"
data-cy="account-tab"
/>
) : null}
{!disableAnonymous ? (
<Tab
label={translations.AnonymousLogin.anonymousAuthHeader}
value="anon"
data-cy="anon-tab"
/>
) : null}
</Tabs>
</AppBar>
<DialogContent>
{currentTab === 'social' ? (
<SocialAuth onClose={onClose} onUser={setUser} />
) : null}
{currentTab === 'account' ? (
<AccountAuth onClose={onClose} onUser={setUser} />
) : null}
{currentTab === 'anon' ? (
<AnonAuth onClose={onClose} onUser={setUser} />
) : null}
</DialogContent>
</>
)}
</Dialog>
);
};

function getDefaultMode(
oauth: boolean,
password: boolean,
anon: boolean
): TabType {
if (oauth) {
return 'social';
}

if (password) {
return 'account';
}

if (anon) {
return 'anon';
}

return null;
}

export default Login;
11 changes: 11 additions & 0 deletions frontend/src/auth/modal/account/Register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Person, Email, VpnKey } from '@mui/icons-material';
import { register } from '../../../api';
import { validate } from 'isemail';
import UserContext from '../../Context';
import useBackendCapabilities from 'global/useBackendCapabilities';

type RegisterProps = {
onClose: () => void;
Expand All @@ -38,6 +39,7 @@ const Register = ({ onClose }: RegisterProps) => {
const [generalError, setGeneralError] = useState<string | null>(null);
const [isSuccessful, setIsSuccessful] = useState(false);
const { setUser } = useContext(UserContext);
const { disablePasswordRegistration } = useBackendCapabilities();

const validEmail = useMemo(() => {
return validate(registerEmail);
Expand Down Expand Up @@ -78,6 +80,15 @@ const Register = ({ onClose }: RegisterProps) => {
onClose,
]);

if (disablePasswordRegistration) {
return (
<Alert severity="error">
Registration is disabled by your administrator. Ask your administrator
to create an account for you.
</Alert>
);
}

return (
<Wrapper
header={translations.header}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/common/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface BackendCapabilities {
licenced: boolean;
oAuth: OAuthAvailabilities;
disableAnonymous: boolean;
disablePasswords: boolean;
disablePasswordRegistration: boolean;
}

export interface OAuthAvailabilities {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/global/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const backendCapabilitiesState = atom<BackendCapabilities>({
licenced: true,
selfHosted: false,
disableAnonymous: false,
disablePasswords: false,
disablePasswordRegistration: false,
oAuth: {
google: false,
github: false,
Expand Down
2 changes: 2 additions & 0 deletions self-hosting/docker-compose.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ services:
BASE_URL: http://localhost:80 # This must be the URL of the frontend app once deployed. Only useful if you need OAuth, SendGrid or Stripe
SECURE_COOKIES: 'false' # You can set this to true if you are using HTTPS. This is more secure.
DISABLE_ANONYMOUS_LOGIN: 'false' # Set to true to disable anonymous accounts
DISABLE_PASSWORD_LOGIN: 'false' # Set to true to disable password accounts (email accounts).
DISABLE_PASSWORD_REGISTRATION: 'false' # Set to true to disable password accounts registration (but not login!)

# -- OAuth: Set these to enable OAuth authentication for one or more provider. This is optional. --
TWITTER_KEY:
Expand Down

0 comments on commit b16559b

Please sign in to comment.