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:
@@ -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`
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const slug = 'users';
|
|||||||
export default buildConfig({
|
export default buildConfig({
|
||||||
admin: {
|
admin: {
|
||||||
user: 'users',
|
user: 'users',
|
||||||
|
autoLogin: false,
|
||||||
},
|
},
|
||||||
collections: [
|
collections: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user