Skip to content
Merged
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
6 changes: 4 additions & 2 deletions apps/cli/lib/admin-credentials.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { decodePassword } from '@studio/common/lib/passwords';
import { decodeAdminPassword } from '@studio/common/lib/passwords';
import { ServerConfig } from 'cli/lib/types/wordpress-server-ipc';

type AdminCredentialsConfig = Pick<
Expand Down Expand Up @@ -28,9 +28,11 @@ export function shouldSetAdminCredentials( config: AdminCredentialsConfig ): boo
export function getSetAdminCredentialsRequestBody(
config: AdminCredentialsConfig
): SetAdminCredentialsRequestBody {
// The password is always sent: creating a user requires one, so omitting it fails the
// request outright when adminUsername names a user that does not exist yet.
return {
action: 'set_admin_password',
...( config.adminPassword && { password: decodePassword( config.adminPassword ) } ),
password: decodeAdminPassword( config.adminPassword ),
...( config.adminUsername && { username: config.adminUsername } ),
...( config.adminEmail && { email: config.adminEmail } ),
};
Expand Down
12 changes: 8 additions & 4 deletions apps/cli/lib/native-php/site-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import fs from 'node:fs';
import path from 'node:path';
import { DEFAULT_LOCALE } from '@studio/common/lib/locale';
import { escapePhpSingleQuotedString } from '@studio/common/lib/mu-plugins';
import { decodePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_EMAIL,
DEFAULT_ADMIN_USERNAME,
decodeAdminPassword,
} from '@studio/common/lib/passwords';
import { type NativePhpSupportedVersion } from '@studio/common/lib/php-binary-metadata';
import { getWpCliPharPath } from 'cli/lib/dependency-management/paths';
import { ensurePhpBinaryAvailable } from '../dependency-management/php-binary';
Expand Down Expand Up @@ -167,9 +171,9 @@ export async function installWordPress(
}

const siteTitle = config.siteTitle ?? 'My WordPress Website';
const username = config.adminUsername ?? 'admin';
const password = config.adminPassword ? decodePassword( config.adminPassword ) : 'password';
const email = config.adminEmail ?? 'admin@localhost.com';
const username = config.adminUsername ?? DEFAULT_ADMIN_USERNAME;
const password = decodeAdminPassword( config.adminPassword );
const email = config.adminEmail ?? DEFAULT_ADMIN_EMAIL;
const siteUrl = config.absoluteUrl ?? `http://localhost:${ config.port }`;
// WP-CLI defaults to en_US; Studio's DEFAULT_LOCALE of "en" is not a WP locale code.
const locale =
Expand Down
12 changes: 11 additions & 1 deletion apps/cli/lib/tests/admin-credentials.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { encodePassword } from '@studio/common/lib/passwords';
import { DEFAULT_ADMIN_PASSWORD, encodePassword } from '@studio/common/lib/passwords';
import {
getSetAdminCredentialsRequestBody,
requestSetAdminCredentials,
Expand Down Expand Up @@ -27,6 +27,16 @@ describe( 'admin credentials', () => {
} );
} );

it( 'falls back to the default password when the site has none stored', () => {
// A site configured with only a username must still send a password: creating the
// user fails without one, which previously left the site unable to start.
expect( getSetAdminCredentialsRequestBody( { adminUsername: 'admine' } ) ).toEqual( {
action: 'set_admin_password',
username: 'admine',
password: DEFAULT_ADMIN_PASSWORD,
} );
} );

it( 'serializes the admin API body as form data', () => {
const params = toUrlSearchParams( {
action: 'set_admin_password',
Expand Down
18 changes: 14 additions & 4 deletions apps/local/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ import { importIpcEventSchema } from '@studio/common/lib/import-export-events';
import { isErrnoException } from '@studio/common/lib/is-errno-exception';
import { getLocalMediaMimeType } from '@studio/common/lib/media-mime';
import { getAuthenticationUrl, getSignUpUrl } from '@studio/common/lib/oauth';
import { decodePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_USERNAME,
decodeAdminPassword,
decodePassword,
} from '@studio/common/lib/passwords';
import {
getInstructionsLengthBucket,
isTracksEventName,
Expand Down Expand Up @@ -973,12 +977,18 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
if ( ( updated.enableXdebug ?? false ) !== ( current.enableXdebug ?? false ) ) {
options.xdebug = updated.enableXdebug ?? false;
}
if ( ( updated.adminUsername ?? 'admin' ) !== ( current.adminUsername ?? 'admin' ) ) {
if (
( updated.adminUsername ?? DEFAULT_ADMIN_USERNAME ) !==
( current.adminUsername ?? DEFAULT_ADMIN_USERNAME )
) {
options.adminUsername = updated.adminUsername;
}
if ( ( updated.adminPassword ?? '' ) !== ( current.adminPassword ?? '' ) ) {
if (
decodeAdminPassword( updated.adminPassword ) !==
decodeAdminPassword( current.adminPassword )
) {
// The CLI expects a plaintext password (it encodes before saving).
options.adminPassword = decodePassword( updated.adminPassword ?? '' );
options.adminPassword = decodeAdminPassword( updated.adminPassword );
}
if ( ( updated.adminEmail ?? '' ) !== ( current.adminEmail ?? '' ) ) {
options.adminEmail = updated.adminEmail;
Expand Down
8 changes: 6 additions & 2 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ import { isMultisite } from '@studio/common/lib/is-multisite';
import { checkMaintenanceFile } from '@studio/common/lib/maintenance-file';
import { getLocalMediaMimeType } from '@studio/common/lib/media-mime';
import { getAuthenticationUrl } from '@studio/common/lib/oauth';
import { decodePassword, encodePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_PASSWORD,
decodePassword,
encodePassword,
} from '@studio/common/lib/passwords';
import { isTracksEventName } from '@studio/common/lib/record-tracks-event';
import { sanitizeFolderName } from '@studio/common/lib/sanitize-folder-name';
import {
Expand Down Expand Up @@ -717,7 +721,7 @@ export async function removeWordPressSkillFromAllSites(

const DEBUG_LOG_MAX_LINES = 50;
const PROCESS_MANAGER_HOME = nodePath.join( os.homedir(), '.studio', 'daemon' );
const DEFAULT_ENCODED_PASSWORD = encodePassword( 'password' );
const DEFAULT_ENCODED_PASSWORD = encodePassword( DEFAULT_ADMIN_PASSWORD );

function readWordPressDebugLog( sitePath: string ): string[] | undefined {
const debugLogPath = nodePath.join( sitePath, DEBUG_LOG_RELATIVE_PATH );
Expand Down
10 changes: 7 additions & 3 deletions apps/ui/src/components/create-site-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { DEFAULT_WORDPRESS_VERSION } from '@studio/common/constants';
import { generateCustomDomainFromSiteName } from '@studio/common/lib/domains';
import { generatePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_EMAIL,
DEFAULT_ADMIN_USERNAME,
generatePassword,
} from '@studio/common/lib/passwords';
import { getLatestVersionLabel } from '@studio/common/lib/wordpress-versions';
import { RecommendedPHPVersion } from '@studio/common/types/php-versions';
import { BaseControl, CheckboxControl, TextControl } from '@wordpress/components';
Expand Down Expand Up @@ -113,9 +117,9 @@ function createDefaultFormData(): FormData {
useCustomDomain: false,
customDomain: '',
enableHttps: false,
adminUsername: 'admin',
adminUsername: DEFAULT_ADMIN_USERNAME,
adminPassword: generatePassword(),
adminEmail: 'admin@localhost.com',
adminEmail: DEFAULT_ADMIN_EMAIL,
};
}

Expand Down
12 changes: 8 additions & 4 deletions apps/ui/src/components/site-overview-view/admin-section.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { decodePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_EMAIL,
DEFAULT_ADMIN_USERNAME,
decodeAdminPassword,
} from '@studio/common/lib/passwords';
import { __ } from '@wordpress/i18n';
import { CopyButton } from '@/components/copy-button';
import styles from './cards.module.css';
Expand Down Expand Up @@ -28,9 +32,9 @@ function CredentialRow( {
}

export function AdminSection( { site }: { site: SiteDetails } ) {
const username = site.adminUsername ?? 'admin';
const password = site.adminPassword ? decodePassword( site.adminPassword ) : '';
const email = site.adminEmail ?? 'admin@localhost.com';
const username = site.adminUsername ?? DEFAULT_ADMIN_USERNAME;
const password = decodeAdminPassword( site.adminPassword );
const email = site.adminEmail ?? DEFAULT_ADMIN_EMAIL;

return (
<CardSection>
Expand Down
13 changes: 9 additions & 4 deletions apps/ui/src/components/site-settings-view/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { DEFAULT_WORDPRESS_VERSION } from '@studio/common/constants';
import { generateCustomDomainFromSiteName } from '@studio/common/lib/domains';
import { decodePassword, encodePassword } from '@studio/common/lib/passwords';
import {
DEFAULT_ADMIN_EMAIL,
DEFAULT_ADMIN_USERNAME,
decodeAdminPassword,
encodePassword,
} from '@studio/common/lib/passwords';
import { RecommendedPHPVersion } from '@studio/common/types/php-versions';
import { CheckboxControl } from '@wordpress/components';
import { DataForm, useFormValidity } from '@wordpress/dataviews';
Expand Down Expand Up @@ -79,9 +84,9 @@ function initialFormData( site: SiteDetails, installedWpVersion?: string ): Form
useCustomDomain: Boolean( site.customDomain ),
customDomain: site.customDomain ?? '',
enableHttps: site.enableHttps ?? false,
adminUsername: site.adminUsername ?? 'admin',
adminPassword: decodePassword( site.adminPassword ?? '' ) || 'password',
adminEmail: site.adminEmail || 'admin@localhost.com',
adminUsername: site.adminUsername ?? DEFAULT_ADMIN_USERNAME,
adminPassword: decodeAdminPassword( site.adminPassword ),
adminEmail: site.adminEmail || DEFAULT_ADMIN_EMAIL,
enableXdebug: site.enableXdebug ?? false,
enableDebugLog: site.enableDebugLog ?? false,
enableDebugDisplay: site.enableDebugDisplay ?? false,
Expand Down
19 changes: 19 additions & 0 deletions packages/common/lib/passwords.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ import { __, sprintf } from '@wordpress/i18n';

export { generatePassword };

/**
* Defaults for a site's admin credentials, shared by both front ends and the CLI so an
* unset value resolves to the same thing everywhere. A site whose config predates these
* fields — or that only ever had one of them set — falls back to these on start.
*/
export const DEFAULT_ADMIN_USERNAME = 'admin';
export const DEFAULT_ADMIN_PASSWORD = 'password';
export const DEFAULT_ADMIN_EMAIL = 'admin@localhost.com';

/**
* Resolves a site's stored (encoded) admin password to plain text, falling back to the
* default when nothing is stored. Callers that send credentials to WordPress must use
* this rather than skipping the field: omitting the password fails the admin API when
* the username names a user that does not exist yet.
*/
export function decodeAdminPassword( encodedPassword?: string ): string {
return encodedPassword ? decodePassword( encodedPassword ) : DEFAULT_ADMIN_PASSWORD;
}

/**
* Generates a random, Base64-encoded password.
*
Expand Down
Loading