Skip to content

Commit 733fc0b

Browse files
authored
feat: auto-login in config capability (#3009)
This is useful when developing/testing, where logging after every change can be cumbersome.
1 parent 2fc03f1 commit 733fc0b

File tree

17 files changed

+62
-34
lines changed

17 files changed

+62
-34
lines changed

contributing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ The directory split up in this way specifically to reduce friction when creating
4949

5050
The following command will start Payload with your config: `yarn dev my-test-dir`. This command will start up Payload using your config and refresh a test database on every restart.
5151

52+
By default, it will automatically log you in with the default credentials. To disable that, you can either pass in the --no-auto-login flag (example: `yarn dev my-test-dir --no-auto-login`) or set the `PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN` environment variable to `false`.
53+
5254
If you wish to use to your own Mongo database for the `test` directory instead of using the in memory database, all you need to do is add the following env vars to the `test/dev.ts` file:
5355

5456
- `process.env.NODE_ENV`

src/admin/components/utilities/Auth/index.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
2626
admin: {
2727
user: userSlug,
2828
inactivityRoute: logoutInactivityRoute,
29+
autoLogin,
2930
},
3031
serverURL,
3132
routes: {
@@ -143,6 +144,28 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
143144
setUser(json.user);
144145
} else if (json?.token) {
145146
setToken(json.token);
147+
} else if (autoLogin) {
148+
// auto log-in with the provided autoLogin credentials. This is used in dev mode
149+
// so you don't have to log in over and over again
150+
const autoLoginResult = await requests.post(`${serverURL}${api}/${userSlug}/login`, {
151+
body: JSON.stringify({
152+
email: autoLogin.email,
153+
password: autoLogin.password,
154+
}),
155+
headers: {
156+
'Accept-Language': i18n.language,
157+
'Content-Type': 'application/json',
158+
},
159+
});
160+
if (autoLoginResult.status === 200) {
161+
const autoLoginJson = await autoLoginResult.json();
162+
setUser(autoLoginJson.user);
163+
if (autoLoginJson?.token) {
164+
setToken(autoLoginJson.token);
165+
}
166+
} else {
167+
setUser(null);
168+
}
146169
} else {
147170
setUser(null);
148171
}
@@ -153,7 +176,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
153176
};
154177

155178
fetchMe();
156-
}, [i18n, setToken, api, serverURL, userSlug]);
179+
}, [i18n, setToken, api, serverURL, userSlug, autoLogin]);
157180

158181
// When location changes, refresh cookie
159182
useEffect(() => {

src/config/schema.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ export default joi.object({
6666
),
6767
logoutRoute: joi.string(),
6868
inactivityRoute: joi.string(),
69+
autoLogin: joi.alternatives()
70+
.try(
71+
joi.object().keys({
72+
email: joi.string(),
73+
password: joi.string(),
74+
}),
75+
joi.boolean(),
76+
),
6977
components: joi.object()
7078
.keys({
7179
routes: joi.array()

src/config/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,13 @@ export type InitOptions = {
116116
* See Pino Docs for options: https://getpino.io/#/docs/api?id=options
117117
*/
118118
loggerOptions?: LoggerOptions;
119-
config?: Promise<SanitizedConfig>
119+
120+
/**
121+
* Sometimes, with the local API, you might need to pass a config file directly, for example, serverless on Vercel
122+
* The passed config should match the config file, and if it doesn't, there could be mismatches between the admin UI
123+
* and the backend functionality
124+
*/
125+
config?: Promise<SanitizedConfig>;
120126
};
121127

122128
/**
@@ -275,6 +281,13 @@ export type Config = {
275281
logoutRoute?: string;
276282
/** The route the user will be redirected to after being inactive for too long. */
277283
inactivityRoute?: string;
284+
/** Automatically log in as a user when visiting the admin dashboard. */
285+
autoLogin?: false | {
286+
/** The email address of the user to login as */
287+
email: string;
288+
/** The password of the user to login as */
289+
password: string;
290+
}
278291
/**
279292
* Add extra and/or replace built-in components with custom components
280293
*

test/_community/e2e.spec.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Page } from '@playwright/test';
22
import { expect, test } from '@playwright/test';
33
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
44
import { initPayloadE2E } from '../helpers/configHelpers';
5-
import { login } from '../helpers';
65

76
const { beforeAll, describe } = test;
87
let url: AdminUrlUtil;
@@ -16,11 +15,6 @@ describe('Admin Panel', () => {
1615

1716
const context = await browser.newContext();
1817
page = await context.newPage();
19-
20-
await login({
21-
page,
22-
serverURL,
23-
});
2418
});
2519

2620
test('example test', async () => {

test/access-control/e2e.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
33
import payload from '../../src';
44
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
55
import { initPayloadE2E } from '../helpers/configHelpers';
6-
import { login } from '../helpers';
76
import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug, docLevelAccessSlug, unrestrictedSlug } from './config';
87
import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
98
import wait from '../../src/utilities/wait';
@@ -37,8 +36,6 @@ describe('access control', () => {
3736

3837
const context = await browser.newContext();
3938
page = await context.newPage();
40-
41-
await login({ page, serverURL });
4239
});
4340

4441
test('field without read access should not show', async () => {

test/admin/e2e.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
33
import payload from '../../src';
44
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
55
import { initPayloadE2E } from '../helpers/configHelpers';
6-
import { login, saveDocAndAssert } from '../helpers';
6+
import { saveDocAndAssert } from '../helpers';
77
import type { Post } from './config';
88
import { globalSlug, slug } from './shared';
99
import { mapAsync } from '../../src/utilities/mapAsync';
@@ -26,8 +26,6 @@ describe('admin', () => {
2626

2727
const context = await browser.newContext();
2828
page = await context.newPage();
29-
30-
await login({ page, serverURL });
3129
});
3230

3331
afterEach(async () => {

test/auth/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const slug = 'users';
99
export default buildConfig({
1010
admin: {
1111
user: 'users',
12+
autoLogin: false,
1213
},
1314
collections: [
1415
{

test/buildConfig.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ export function buildConfig(config?: Partial<Config>): Promise<SanitizedConfig>
1111
},
1212
...config,
1313
};
14-
1514
baseConfig.admin = {
15+
autoLogin: process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' ? false : {
16+
17+
password: 'test',
18+
},
1619
...(baseConfig.admin || {}),
1720
webpack: (webpackConfig) => {
1821
const existingConfig = typeof config?.admin?.webpack === 'function'

test/dev.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ process.env.PAYLOAD_CONFIG_PATH = configPath;
2525

2626
process.env.PAYLOAD_DROP_DATABASE = 'true';
2727

28+
if (process.argv.includes('--no-auto-login') && process.env.NODE_ENV !== 'production') {
29+
process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN = 'true';
30+
}
31+
2832
const expressApp = express();
2933

3034
const startDev = async () => {

0 commit comments

Comments
 (0)