Skip to content

Commit

Permalink
Sort out rebasing
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira committed Feb 22, 2024
1 parent 591952b commit 045ba91
Show file tree
Hide file tree
Showing 17 changed files with 105 additions and 162 deletions.
4 changes: 0 additions & 4 deletions docs/data/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,6 @@ const pages: MuiPage[] = [
pathname: '/toolpad/how-to-guides/embed-pages',
title: 'Embedding Toolpad pages',
},
{
pathname: '/toolpad/how-to-guides/basic-auth',
title: 'Enable basic auth',
},
{
pathname: '/toolpad/how-to-guides/editor-path',
title: 'Troubleshoot missing editor',
Expand Down
11 changes: 10 additions & 1 deletion docs/data/toolpad/concepts/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Please make sure to keep this secret safe and do not share it with anyone!

## Authentication providers

In the authentication settings, you can set up one or more authentication providers for users to be able to sign in with, such as GitHub and Google.
In the authentication settings, you can set up one or more authentication providers for users to be able to sign in with, such as GitHub and Google, or a username/password combination.

If any authentication providers are set, only authenticated users are able to access your Toolpad application.

Expand Down Expand Up @@ -88,6 +88,15 @@ Follow these steps to configure your Azure AD client and get the necessary envir

With the Azure AD provider, only existing users of your Azure AD application will be able to sign in.

### Credentials (username and password)

| Variable | Description |
| :---------------------- | :--------------------------------- |
| `TOOLPAD_AUTH_USERNAME` | Username to be entered to sign in. |
| `TOOLPAD_AUTH_PASSWORD` | Password to be entered to sign in. |

{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/concepts/authorization/credentials.png", "alt": "Credentials authentication", "caption": "Credentials authentication", "aspectRatio": 6 }}

## Restricted domains

In the authentication settings, you can specify one or more domains (such as `mui.com`) to restrict user authentication based on the signed-in user's email address.
Expand Down
16 changes: 0 additions & 16 deletions docs/data/toolpad/how-to-guides/basic-auth.md

This file was deleted.

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion packages/toolpad-app/src/runtime/SignInPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ export default function SignInPage() {
'There was an error with your authentication provider configuration.',
);
} else if (authError === 'MissingSecretError') {
setErrorSnackbarMessage('Missing secret for authentication. Please provide a secret.');
setErrorSnackbarMessage(
'Missing secret for authentication. Please provide the TOOLPAD_AUTH_SECRET environment variable.',
);
} else if (authError === 'CredentialsSignin') {
setErrorSnackbarMessage('Invalid credentials.');
} else if (authError) {
setErrorSnackbarMessage('An authentication error occurred.');
}
Expand Down
51 changes: 33 additions & 18 deletions packages/toolpad-app/src/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ async function getAuthProviders(

export async function getRequireAuthentication(project: ToolpadProject): Promise<boolean> {
const authProviders = await getAuthProviders(project);
return authProviders.length > 0;

// @TODO: Eventually can deprecate old basic auth setup and remove the TOOLPAD_BASIC_AUTH_USER check
return authProviders.length > 0 || !!process.env.TOOLPAD_BASIC_AUTH_USER;
}

function getMappedRoles(
Expand Down Expand Up @@ -141,25 +143,38 @@ export function createAuthHandler(project: ToolpadProject): Router {
name: 'Credentials',
async authorize({ username, password }) {
if (process.env.NODE_ENV !== 'test') {
throw new Error('Credentials authentication provider can only be used in test mode.');
}
// @TODO: Eventually can deprecate old environment variable names (TOOLPAD_BASIC_AUTH...)
const authUsername =
process.env.TOOLPAD_AUTH_USERNAME ?? process.env.TOOLPAD_BASIC_AUTH_USER;
const authPassword =
process.env.TOOLPAD_AUTH_PASSWORD ?? process.env.TOOLPAD_BASIC_AUTH_PASSWORD;

if (
authUsername &&
authPassword &&
username === authUsername &&
password === authPassword
) {
return { id: 'user', name: 'User', email: 'user@mui.com', roles: [] };
}
} else {
if (username === 'admin' && password === 'admin') {
return {
id: 'admin',
name: 'Lord Admin',
email: 'admin@example.com',
roles: ['mock-admin'],
};
}
if (username === 'mui' && password === 'mui') {
return { id: 'mui', name: 'Mr. MUI 2024', email: 'test@mui.com', roles: [] };
}
if (username === 'test' && password === 'test') {
return { id: 'test', name: 'Miss Test', email: 'test@example.com', roles: [] };
}

if (username === 'admin' && password === 'admin') {
return {
id: 'admin',
name: 'Lord Admin',
email: 'admin@example.com',
roles: ['mock-admin'],
};
}
if (username === 'mui' && password === 'mui') {
return { id: 'mui', name: 'Mr. MUI 2024', email: 'test@mui.com', roles: [] };
return null;
}
if (username === 'test' && password === 'test') {
return { id: 'test', name: 'Miss Test', email: 'test@example.com', roles: [] };
}

return null;
},
});

Expand Down
29 changes: 0 additions & 29 deletions packages/toolpad-app/src/server/basicAuth.ts

This file was deleted.

27 changes: 1 addition & 26 deletions packages/toolpad-app/src/server/config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
type BasicAuthConfig =
| {
basicAuthUser: string;
basicAuthPassword: string;
}
| {
basicAuthUser?: undefined;
basicAuthPassword?: undefined;
};

export type ServerConfig = {
databaseUrl?: string;
googleSheetsClientId?: string;
googleSheetsClientSecret?: string;
encryptionKeys: string[];
basicAuthUser?: string;
basicAuthPassword?: string;
} & BasicAuthConfig;
};

function readConfig(): ServerConfig {
if (typeof window !== 'undefined') {
Expand All @@ -26,20 +14,7 @@ function readConfig(): ServerConfig {
const encryptionKeys: string[] =
process.env.TOOLPAD_ENCRYPTION_KEYS?.split(/\s+/).filter(Boolean) ?? [];

let basicAuthConfig: BasicAuthConfig = {};
if (process.env.TOOLPAD_BASIC_AUTH_USER && process.env.TOOLPAD_BASIC_AUTH_PASSWORD) {
basicAuthConfig = {
basicAuthUser: process.env.TOOLPAD_BASIC_AUTH_USER,
basicAuthPassword: process.env.TOOLPAD_BASIC_AUTH_PASSWORD,
};
} else if (process.env.TOOLPAD_BASIC_AUTH_USER) {
throw new Error(
`Basic Auth user configured without password. Please provide the TOOLPAD_BASIC_AUTH_PASSWORD environment variable.`,
);
}

return {
...basicAuthConfig,
databaseUrl: process.env.TOOLPAD_DATABASE_URL,
googleSheetsClientId: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_ID,
googleSheetsClientSecret: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_SECRET,
Expand Down
10 changes: 0 additions & 10 deletions packages/toolpad-app/src/server/toolpadAppServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as express from 'express';
import serializeJavascript from 'serialize-javascript';
import { ToolpadProject } from './localMode';
import { asyncHandler } from '../utils/express';
import { basicAuthUnauthorized, checkBasicAuthHeader } from './basicAuth';
import { createRpcServer } from './runtimeRpcServer';
import { createRpcHandler } from './rpc';
import { RUNTIME_CONFIG_WINDOW_PROPERTY, INITIAL_STATE_WINDOW_PROPERTY } from '../constants';
Expand Down Expand Up @@ -54,15 +53,6 @@ export async function createProdHandler(project: ToolpadProject) {

handler.use(express.static(project.getAppOutputFolder(), { index: false }));

// Allow static assets, block everything else
handler.use((req, res, next) => {
if (checkBasicAuthHeader(req.headers.authorization ?? null)) {
next();
return;
}
basicAuthUnauthorized(res);
});

const hasAuthentication = await getRequireAuthentication(project);
if (hasAuthentication) {
const authHandler = createAuthHandler(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from '@mui/x-data-grid';
import GitHubIcon from '@mui/icons-material/GitHub';
import GoogleIcon from '@mui/icons-material/Google';
import PasswordIcon from '@mui/icons-material/Password';
import { TabContext, TabList } from '@mui/lab';
import { updateArray } from '@mui/toolpad-utils/immutability';
import * as appDom from '@mui/toolpad-core/appDom';
Expand All @@ -60,6 +61,14 @@ const AUTH_PROVIDER_OPTIONS = new Map<string, AuthProviderOption>([
hasRoles: true,
},
],
[
'credentials',
{
name: 'Credentials',
icon: <PasswordIcon fontSize="small" />,
hasRoles: false,
},
],
]);

export function AppAuthenticationEditor() {
Expand Down
40 changes: 40 additions & 0 deletions test/integration/auth/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as path from 'path';
import * as url from 'url';
import { test, expect } from '../../playwright/localTest';
import { tryCredentialsSignIn } from './shared';

const currentDirectory = url.fileURLToPath(new URL('.', import.meta.url));

test.use({
ignoreConsoleErrors: [
/Failed to load resource: the server responded with a status of 401 \(Unauthorized\)/,
],
});

test.use({
projectConfig: {
template: path.resolve(currentDirectory, './fixture-basic'),
},
localAppConfig: {
cmd: 'start',
env: {
TOOLPAD_AUTH_SECRET: 'donttellanyone',
TOOLPAD_AUTH_USERNAME: 'foo',
TOOLPAD_AUTH_PASSWORD: 'bar',
},
},
});

test('Must be authenticated with correct credentials to access app', async ({ page }) => {
await page.goto('/prod/pages/mypage');

// Sign in with invalid credentials
await tryCredentialsSignIn(page, 'foo', 'wrongpassword');
await expect(page).toHaveURL(/\/prod\/signin/);
await expect(page.getByText('Invalid credentials.')).toBeVisible();

// Sign in with valid credentials
await tryCredentialsSignIn(page, 'foo', 'bar');
await expect(page).toHaveURL(/\/prod\/pages\/mypage/);
await expect(page.getByText('message: hello world')).toBeVisible();
});
2 changes: 1 addition & 1 deletion test/integration/auth/domain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('Must be authenticated with valid domain to access app', async ({ page, req
await expect(page).toHaveURL(/\/prod\/signin/);

// Access is blocked to API route
const res = await request.post('/prod/api/data/page/hello');
const res = await request.post('/prod/api/data/page/getMySession');
expect(res.status()).toBe(401);

// Sign in with invalid domain
Expand Down
5 changes: 5 additions & 0 deletions test/integration/auth/fixture-basic/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: application
spec:
authentication:
providers: [{ provider: credentials }]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v1
kind: page
spec:
id: 5q1xd0t
title: Page 1
title: Page
content:
- component: PageRow
name: pageRow
Expand Down
55 changes: 0 additions & 55 deletions test/integration/basic-auth/prod.spec.ts

This file was deleted.

0 comments on commit 045ba91

Please sign in to comment.