feat: auto-login in config capability (#3009)

This is useful when developing/testing, where logging after every change can be cumbersome.
This commit is contained in:
Alessio Gravili
2023-07-17 22:35:58 +02:00
committed by GitHub
parent 2fc03f196e
commit 733fc0b2d0
17 changed files with 62 additions and 34 deletions

View File

@@ -49,6 +49,8 @@ The directory split up in this way specifically to reduce friction when creating
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. 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.
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`.
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: 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:
- `process.env.NODE_ENV` - `process.env.NODE_ENV`

View File

@@ -26,6 +26,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
admin: { admin: {
user: userSlug, user: userSlug,
inactivityRoute: logoutInactivityRoute, inactivityRoute: logoutInactivityRoute,
autoLogin,
}, },
serverURL, serverURL,
routes: { routes: {
@@ -143,6 +144,28 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setUser(json.user); setUser(json.user);
} else if (json?.token) { } else if (json?.token) {
setToken(json.token); setToken(json.token);
} else if (autoLogin) {
// auto log-in with the provided autoLogin credentials. This is used in dev mode
// so you don't have to log in over and over again
const autoLoginResult = await requests.post(`${serverURL}${api}/${userSlug}/login`, {
body: JSON.stringify({
email: autoLogin.email,
password: autoLogin.password,
}),
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
},
});
if (autoLoginResult.status === 200) {
const autoLoginJson = await autoLoginResult.json();
setUser(autoLoginJson.user);
if (autoLoginJson?.token) {
setToken(autoLoginJson.token);
}
} else {
setUser(null);
}
} else { } else {
setUser(null); setUser(null);
} }
@@ -153,7 +176,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
}; };
fetchMe(); fetchMe();
}, [i18n, setToken, api, serverURL, userSlug]); }, [i18n, setToken, api, serverURL, userSlug, autoLogin]);
// When location changes, refresh cookie // When location changes, refresh cookie
useEffect(() => { useEffect(() => {

View File

@@ -66,6 +66,14 @@ export default joi.object({
), ),
logoutRoute: joi.string(), logoutRoute: joi.string(),
inactivityRoute: joi.string(), inactivityRoute: joi.string(),
autoLogin: joi.alternatives()
.try(
joi.object().keys({
email: joi.string(),
password: joi.string(),
}),
joi.boolean(),
),
components: joi.object() components: joi.object()
.keys({ .keys({
routes: joi.array() routes: joi.array()

View File

@@ -116,7 +116,13 @@ export type InitOptions = {
* See Pino Docs for options: https://getpino.io/#/docs/api?id=options * See Pino Docs for options: https://getpino.io/#/docs/api?id=options
*/ */
loggerOptions?: LoggerOptions; loggerOptions?: LoggerOptions;
config?: Promise<SanitizedConfig>
/**
* Sometimes, with the local API, you might need to pass a config file directly, for example, serverless on Vercel
* The passed config should match the config file, and if it doesn't, there could be mismatches between the admin UI
* and the backend functionality
*/
config?: Promise<SanitizedConfig>;
}; };
/** /**
@@ -275,6 +281,13 @@ export type Config = {
logoutRoute?: string; logoutRoute?: string;
/** The route the user will be redirected to after being inactive for too long. */ /** The route the user will be redirected to after being inactive for too long. */
inactivityRoute?: string; inactivityRoute?: string;
/** Automatically log in as a user when visiting the admin dashboard. */
autoLogin?: false | {
/** The email address of the user to login as */
email: string;
/** The password of the user to login as */
password: string;
}
/** /**
* Add extra and/or replace built-in components with custom components * Add extra and/or replace built-in components with custom components
* *

View File

@@ -2,7 +2,6 @@ import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login } from '../helpers';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;
let url: AdminUrlUtil; let url: AdminUrlUtil;
@@ -16,11 +15,6 @@ describe('Admin Panel', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({
page,
serverURL,
});
}); });
test('example test', async () => { test('example test', async () => {

View File

@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login } from '../helpers';
import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug, docLevelAccessSlug, unrestrictedSlug } from './config'; import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug, docLevelAccessSlug, unrestrictedSlug } from './config';
import type { ReadOnlyCollection, RestrictedVersion } from './payload-types'; import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
import wait from '../../src/utilities/wait'; import wait from '../../src/utilities/wait';
@@ -37,8 +36,6 @@ describe('access control', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
test('field without read access should not show', async () => { test('field without read access should not show', async () => {

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { saveDocAndAssert } from '../helpers';
import type { Post } from './config'; import type { Post } from './config';
import { globalSlug, slug } from './shared'; import { globalSlug, slug } from './shared';
import { mapAsync } from '../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
@@ -26,8 +26,6 @@ describe('admin', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
afterEach(async () => { afterEach(async () => {

View File

@@ -9,6 +9,7 @@ export const slug = 'users';
export default buildConfig({ export default buildConfig({
admin: { admin: {
user: 'users', user: 'users',
autoLogin: false,
}, },
collections: [ collections: [
{ {

View File

@@ -11,8 +11,11 @@ export function buildConfig(config?: Partial<Config>): Promise<SanitizedConfig>
}, },
...config, ...config,
}; };
baseConfig.admin = { baseConfig.admin = {
autoLogin: process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' ? false : {
email: 'dev@payloadcms.com',
password: 'test',
},
...(baseConfig.admin || {}), ...(baseConfig.admin || {}),
webpack: (webpackConfig) => { webpack: (webpackConfig) => {
const existingConfig = typeof config?.admin?.webpack === 'function' const existingConfig = typeof config?.admin?.webpack === 'function'

View File

@@ -25,6 +25,10 @@ process.env.PAYLOAD_CONFIG_PATH = configPath;
process.env.PAYLOAD_DROP_DATABASE = 'true'; process.env.PAYLOAD_DROP_DATABASE = 'true';
if (process.argv.includes('--no-auto-login') && process.env.NODE_ENV !== 'production') {
process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN = 'true';
}
const expressApp = express(); const expressApp = express();
const startDev = async () => { const startDev = async () => {

View File

@@ -1,5 +1,4 @@
import { expect, Page, test } from '@playwright/test'; import { expect, Page, test } from '@playwright/test';
import { login } from '../helpers';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;
@@ -12,7 +11,6 @@ describe('field error states', () => {
({ serverURL } = await initPayloadE2E(__dirname)); ({ serverURL } = await initPayloadE2E(__dirname));
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
test('Remove row should remove error states from parent fields', async () => { test('Remove row should remove error states from parent fields', async () => {

View File

@@ -4,7 +4,7 @@ import payload from '../../src';
import { mapAsync } from '../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { saveDocAndAssert } from '../helpers';
import type { import type {
FieldsRelationship as CollectionWithRelationships, FieldsRelationship as CollectionWithRelationships,
RelationOne, RelationOne,
@@ -37,8 +37,6 @@ describe('fields - relationship', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
beforeEach(async () => { beforeEach(async () => {

View File

@@ -4,7 +4,7 @@ import path from 'path';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { saveDocAndAssert } from '../helpers';
import { textDoc } from './collections/Text'; import { textDoc } from './collections/Text';
import { arrayFieldsSlug } from './collections/Array'; import { arrayFieldsSlug } from './collections/Array';
import { pointFieldsSlug } from './collections/Point'; import { pointFieldsSlug } from './collections/Point';
@@ -26,8 +26,6 @@ describe('fields', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
describe('text', () => { describe('text', () => {

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { saveDocAndAssert } from '../helpers';
import type { LocalizedPost } from './payload-types'; import type { LocalizedPost } from './payload-types';
import { localizedPostsSlug } from './config'; import { localizedPostsSlug } from './config';
import { englishTitle, spanishLocale } from './shared'; import { englishTitle, spanishLocale } from './shared';
@@ -39,8 +39,6 @@ describe('Localization', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
describe('localized text', () => { describe('localized text', () => {

View File

@@ -1,5 +1,4 @@
import { expect, Page, test } from '@playwright/test'; import { expect, Page, test } from '@playwright/test';
import { login } from '../helpers';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;
@@ -12,7 +11,6 @@ describe('refresh-permissions', () => {
({ serverURL } = await initPayloadE2E(__dirname)); ({ serverURL } = await initPayloadE2E(__dirname));
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
test('should show test global immediately after allowing access', async () => { test('should show test global immediately after allowing access', async () => {

View File

@@ -6,7 +6,7 @@ import type { Media } from './payload-types';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { saveDocAndAssert } from '../helpers';
import wait from '../../src/utilities/wait'; import wait from '../../src/utilities/wait';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;
@@ -50,8 +50,6 @@ describe('uploads', () => {
}); });
audioDoc = findAudio.docs[0] as Media; audioDoc = findAudio.docs[0] as Media;
await login({ page, serverURL });
}); });
test('should see upload filename in relation list', async () => { test('should see upload filename in relation list', async () => {

View File

@@ -28,7 +28,6 @@ import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { login } from '../helpers';
import { draftSlug, autosaveSlug } from './shared'; import { draftSlug, autosaveSlug } from './shared';
import wait from '../../src/utilities/wait'; import wait from '../../src/utilities/wait';
@@ -44,8 +43,6 @@ describe('versions', () => {
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await login({ page, serverURL });
}); });
describe('draft collections', () => { describe('draft collections', () => {