Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it, vi } from 'vitest';

import { checkSuperadminCredentials } from './check-superadmin-credentials';

const DEFAULTS = { identifier: 'superadmin', password: 'superadmin' };
const SAFE = { identifier: 'admin@example.com', password: 'a-very-long-random-passphrase' };

describe('checkSuperadminCredentials', () => {
it('throws in production when both identifier and password are default', () => {
expect(() => checkSuperadminCredentials(DEFAULTS, { nodeEnv: 'production' })).toThrow(
/Refusing to start/,
);
});

it('throws in production when only the password is default', () => {
expect(() =>
checkSuperadminCredentials(
{ identifier: 'admin@example.com', password: 'superadmin' },
{ nodeEnv: 'production' },
),
).toThrow(/Refusing to start/);
});

it('throws in production when only the identifier is default', () => {
expect(() =>
checkSuperadminCredentials(
{ identifier: 'superadmin', password: 'something-strong-1234' },
{ nodeEnv: 'production' },
),
).toThrow(/Refusing to start/);
});

it('warns in development when defaults are used', () => {
const logger = { warn: vi.fn() };
checkSuperadminCredentials(DEFAULTS, { nodeEnv: 'development', logger });
expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn.mock.calls[0][0]).toMatch(/Default superadmin credentials/);
});

it('warns in staging / non-production environments when defaults are used', () => {
const logger = { warn: vi.fn() };
checkSuperadminCredentials(DEFAULTS, { nodeEnv: 'staging', logger });
expect(logger.warn).toHaveBeenCalledTimes(1);
});

it('is silent in test environment even when defaults are used', () => {
const logger = { warn: vi.fn() };
checkSuperadminCredentials(DEFAULTS, { nodeEnv: 'test', logger });
expect(logger.warn).not.toHaveBeenCalled();
});

it('is silent (and does not throw) when credentials are non-default in production', () => {
const logger = { warn: vi.fn() };
expect(() => checkSuperadminCredentials(SAFE, { nodeEnv: 'production', logger })).not.toThrow();
expect(logger.warn).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';

import { Logger } from '../../../config/logger/vendure-logger';
import { SuperadminCredentials } from '../../../config/vendure-config';

const REMEDIATION_HINT =
'Set `authOptions.superadminCredentials` in your VendureConfig — typically wired from environment variables, e.g. ' +
'`{ identifier: process.env.SUPERADMIN_USERNAME, password: process.env.SUPERADMIN_PASSWORD }`.';

/**
* @description
* Verifies that the configured `superadminCredentials` are not the well-known
* defaults shipped by `@vendure/common`. Used during bootstrap to fail loudly
* in production environments and warn otherwise.
*
* Exported for unit testing — production callers should rely on the default
* `process.env.NODE_ENV` and {@link Logger}.
*/
export function checkSuperadminCredentials(
credentials: Pick<SuperadminCredentials, 'identifier' | 'password'>,
options: { nodeEnv?: string; logger?: Pick<typeof Logger, 'warn'> } = {},
): void {
const usingDefaults =
credentials.identifier === SUPER_ADMIN_USER_IDENTIFIER ||
credentials.password === SUPER_ADMIN_USER_PASSWORD;
if (!usingDefaults) {
return;
}

const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV;
const message =
'Default superadmin credentials are configured. This is INSECURE and must not be used in production. ' +
REMEDIATION_HINT;

if (nodeEnv === 'production') {
throw new Error(`[Vendure] Refusing to start: ${message}`);
}

if (nodeEnv === 'test') {
return;
}

const logger = options.logger ?? Logger;
logger.warn(message);
}
3 changes: 3 additions & 0 deletions packages/core/src/service/services/administrator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { CustomFieldRelationService } from '../helpers/custom-field-relation/cus
import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
import { PasswordCipher } from '../helpers/password-cipher/password-cipher';
import { RequestContextService } from '../helpers/request-context/request-context.service';
import { checkSuperadminCredentials } from '../helpers/utils/check-superadmin-credentials';
import { getChannelPermissions } from '../helpers/utils/get-user-channels-permissions';
import { patchEntity } from '../helpers/utils/patch-entity';

Expand Down Expand Up @@ -322,6 +323,8 @@ export class AdministratorService {
private async ensureSuperAdminExists() {
const { superadminCredentials } = this.configService.authOptions;

checkSuperadminCredentials(superadminCredentials);

const superAdminUser = await this.connection.rawConnection.getRepository(User).findOne({
where: {
identifier: superadminCredentials.identifier,
Expand Down
Loading