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
80 changes: 63 additions & 17 deletions src/WorkOS/Auth/AuthKit/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class Profile {
/**
* Branding config.
*
* @var array{logo_mode: string, logo_attachment_id: int, primary_color: string, heading: string, subheading: string}
* @var array{logo_mode: string, logo_attachment_id: int, card_border_radius: string, button_border_radius: string, page_background: string, card_background: string, card_border: string, heading_color: string, subheading_color: string, button_background: string, button_text: string, secondary_button_background: string, secondary_button_text: string, secondary_button_border: string, links_color: string, heading: string, subheading: string}
*/
private array $branding;

Expand Down Expand Up @@ -201,11 +201,23 @@ public function __construct( array $data ) {
'factors' => array_values( array_filter( (array) ( $data['mfa']['factors'] ?? [] ), 'is_string' ) ),
];
$this->branding = [
'logo_mode' => (string) ( $data['branding']['logo_mode'] ?? self::LOGO_MODE_DEFAULT ),
'logo_attachment_id' => (int) ( $data['branding']['logo_attachment_id'] ?? 0 ),
'primary_color' => (string) ( $data['branding']['primary_color'] ?? '' ),
'heading' => (string) ( $data['branding']['heading'] ?? '' ),
'subheading' => (string) ( $data['branding']['subheading'] ?? '' ),
'logo_mode' => (string) ( $data['branding']['logo_mode'] ?? self::LOGO_MODE_DEFAULT ),
'logo_attachment_id' => (int) ( $data['branding']['logo_attachment_id'] ?? 0 ),
'card_border_radius' => (string) ( $data['branding']['card_border_radius'] ?? '' ),
'button_border_radius' => (string) ( $data['branding']['button_border_radius'] ?? '' ),
'page_background' => (string) ( $data['branding']['page_background'] ?? '' ),
'card_background' => (string) ( $data['branding']['card_background'] ?? '' ),
'card_border' => (string) ( $data['branding']['card_border'] ?? '' ),
'heading_color' => (string) ( $data['branding']['heading_color'] ?? '' ),
'subheading_color' => (string) ( $data['branding']['subheading_color'] ?? '' ),
'button_background' => (string) ( $data['branding']['button_background'] ?? '' ),
'button_text' => (string) ( $data['branding']['button_text'] ?? '' ),
'secondary_button_background' => (string) ( $data['branding']['secondary_button_background'] ?? '' ),
'secondary_button_text' => (string) ( $data['branding']['secondary_button_text'] ?? '' ),
'secondary_button_border' => (string) ( $data['branding']['secondary_button_border'] ?? '' ),
'links_color' => (string) ( $data['branding']['links_color'] ?? '' ),
'heading' => (string) ( $data['branding']['heading'] ?? '' ),
'subheading' => (string) ( $data['branding']['subheading'] ?? '' ),
];
$this->post_login_redirect = (string) ( $data['post_login_redirect'] ?? '' );
$this->forward_query_args = (bool) ( $data['forward_query_args'] ?? false );
Expand Down Expand Up @@ -297,10 +309,27 @@ public static function from_array( array $data ): self {
$organization_id = '';
}

$primary_color = (string) ( $data['branding']['primary_color'] ?? '' );
if ( '' !== $primary_color && ! preg_match( '/^#[0-9a-fA-F]{3,8}$/', $primary_color ) ) {
$primary_color = '';
}
$sanitize_color = static function ( string $value ): string {
return ( '' !== $value && preg_match( '/^#[0-9a-fA-F]{3,8}$/', $value ) ) ? $value : '';
};

$sanitize_radius = static function ( string $v ): string {
$v = trim( $v );
return ( '' !== $v && ctype_digit( $v ) ) ? $v : '';
};
$card_border_radius = $sanitize_radius( (string) ( $data['branding']['card_border_radius'] ?? '' ) );
$button_border_radius = $sanitize_radius( (string) ( $data['branding']['button_border_radius'] ?? '' ) );
$page_background = $sanitize_color( (string) ( $data['branding']['page_background'] ?? '' ) );
$card_background = $sanitize_color( (string) ( $data['branding']['card_background'] ?? '' ) );
$card_border = $sanitize_color( (string) ( $data['branding']['card_border'] ?? '' ) );
$heading_color = $sanitize_color( (string) ( $data['branding']['heading_color'] ?? '' ) );
$subheading_color = $sanitize_color( (string) ( $data['branding']['subheading_color'] ?? '' ) );
$button_background = $sanitize_color( (string) ( $data['branding']['button_background'] ?? '' ) );
$button_text = $sanitize_color( (string) ( $data['branding']['button_text'] ?? '' ) );
$secondary_button_background = $sanitize_color( (string) ( $data['branding']['secondary_button_background'] ?? '' ) );
$secondary_button_text = $sanitize_color( (string) ( $data['branding']['secondary_button_text'] ?? '' ) );
$secondary_button_border = $sanitize_color( (string) ( $data['branding']['secondary_button_border'] ?? '' ) );
$links_color = $sanitize_color( (string) ( $data['branding']['links_color'] ?? '' ) );

$logo_attachment_id = (int) ( $data['branding']['logo_attachment_id'] ?? 0 );

Expand Down Expand Up @@ -341,11 +370,23 @@ public static function from_array( array $data ): self {
'factors' => $mfa_factors,
],
'branding' => [
'logo_mode' => $logo_mode,
'logo_attachment_id' => $logo_attachment_id,
'primary_color' => $primary_color,
'heading' => sanitize_text_field( (string) ( $data['branding']['heading'] ?? '' ) ),
'subheading' => sanitize_text_field( (string) ( $data['branding']['subheading'] ?? '' ) ),
'logo_mode' => $logo_mode,
'logo_attachment_id' => $logo_attachment_id,
'card_border_radius' => $card_border_radius,
'button_border_radius' => $button_border_radius,
'page_background' => $page_background,
'card_background' => $card_background,
'card_border' => $card_border,
'heading_color' => $heading_color,
'subheading_color' => $subheading_color,
'button_background' => $button_background,
'button_text' => $button_text,
'secondary_button_background' => $secondary_button_background,
'secondary_button_text' => $secondary_button_text,
'secondary_button_border' => $secondary_button_border,
'links_color' => $links_color,
'heading' => sanitize_text_field( (string) ( $data['branding']['heading'] ?? '' ) ),
'subheading' => sanitize_text_field( (string) ( $data['branding']['subheading'] ?? '' ) ),
],
'post_login_redirect' => sanitize_text_field( (string) ( $data['post_login_redirect'] ?? '' ) ),
'forward_query_args' => (bool) ( $data['forward_query_args'] ?? false ),
Expand Down Expand Up @@ -384,7 +425,12 @@ public static function defaults(): self {
'branding' => [
'logo_mode' => self::LOGO_MODE_DEFAULT,
'logo_attachment_id' => 0,
'primary_color' => '',
'page_background' => '',
'card_background' => '',
'card_border' => '',
'button_background' => '',
'button_text' => '',
'links_color' => '',
'heading' => __( 'Sign in', 'integration-workos' ),
'subheading' => '',
],
Expand Down Expand Up @@ -620,7 +666,7 @@ public function get_mfa(): array {
/**
* Branding configuration.
*
* @return array{logo_attachment_id: int, primary_color: string, heading: string, subheading: string}
* @return array{logo_attachment_id: int, page_background: string, card_background: string, card_border: string, button_background: string, button_text: string, links_color: string, heading: string, subheading: string}
*/
public function get_branding(): array {
return $this->branding;
Expand Down
123 changes: 101 additions & 22 deletions src/WorkOS/Auth/AuthKit/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,22 @@ private function resolve_branding( Profile $profile ): array {
}

$resolved = [
'logo_url' => $logo_url,
'primary_color' => (string) ( $branding['primary_color'] ?? '' ),
'heading' => (string) ( $branding['heading'] ?? '' ),
'subheading' => (string) ( $branding['subheading'] ?? '' ),
'logo_url' => $logo_url,
'card_border_radius' => (string) ( $branding['card_border_radius'] ?? '' ),
'button_border_radius' => (string) ( $branding['button_border_radius'] ?? '' ),
'page_background' => (string) ( $branding['page_background'] ?? '' ),
'card_background' => (string) ( $branding['card_background'] ?? '' ),
'card_border' => (string) ( $branding['card_border'] ?? '' ),
'heading_color' => (string) ( $branding['heading_color'] ?? '' ),
'subheading_color' => (string) ( $branding['subheading_color'] ?? '' ),
'button_background' => (string) ( $branding['button_background'] ?? '' ),
'button_text' => (string) ( $branding['button_text'] ?? '' ),
'secondary_button_background' => (string) ( $branding['secondary_button_background'] ?? '' ),
'secondary_button_text' => (string) ( $branding['secondary_button_text'] ?? '' ),
'secondary_button_border' => (string) ( $branding['secondary_button_border'] ?? '' ),
'links_color' => (string) ( $branding['links_color'] ?? '' ),
'heading' => (string) ( $branding['heading'] ?? '' ),
'subheading' => (string) ( $branding['subheading'] ?? '' ),
];

/**
Expand Down Expand Up @@ -323,28 +335,95 @@ private function resolve_branding( Profile $profile ): array {
private function branding_style_tag( array $branding ): string {
$rules = [];

// Re-validate the primary color as defense-in-depth. Profile::from_array
// already regex-matches it against a hex pattern on save, but this
// renderer is the last line before raw emission into a CSS context
// where `esc_attr` is semantically wrong. Enforcing the same regex
// here guarantees that whatever arrives in $branding — now or from
// future call sites — cannot introduce a semicolon, closing brace,
// or `</style>` sequence.
$primary = (string) ( $branding['primary_color'] ?? '' );
if ( '' !== $primary && preg_match( '/^#[0-9a-fA-F]{3,8}$/', $primary ) ) {
// A custom primary drops the matching WP-blue hover, so override
// hover to the same color. Deriving a darker shade would require
// a color-math utility and is not worth the added surface area;
// a flat hover reads cleanly enough for a branded palette.
$rules[] = '--wa-primary: ' . $primary . ';';
$rules[] = '--wa-primary-hover: ' . $primary . ';';
// Re-validate colors as defense-in-depth. Profile::from_array already
// regex-matches them on save, but this is the last line before raw
// emission into a CSS context. Enforcing the same regex here ensures
// no semicolon, closing brace, or </style> can be injected regardless
// of call site.
$valid_hex = static function ( string $v ): bool {
return '' !== $v && (bool) preg_match( '/^#[0-9a-fA-F]{3,8}$/', $v );
};

$card_radius = (string) ( $branding['card_border_radius'] ?? '' );
$btn_radius = (string) ( $branding['button_border_radius'] ?? '' );
$page_bg = (string) ( $branding['page_background'] ?? '' );
$card_bg = (string) ( $branding['card_background'] ?? '' );
$card_bdr = (string) ( $branding['card_border'] ?? '' );
$heading_clr = (string) ( $branding['heading_color'] ?? '' );
$sub_clr = (string) ( $branding['subheading_color'] ?? '' );
$btn_bg = (string) ( $branding['button_background'] ?? '' );
$btn_text = (string) ( $branding['button_text'] ?? '' );
$sec_btn_bg = (string) ( $branding['secondary_button_background'] ?? '' );
$sec_btn_text = (string) ( $branding['secondary_button_text'] ?? '' );
$sec_btn_bdr = (string) ( $branding['secondary_button_border'] ?? '' );
$links = (string) ( $branding['links_color'] ?? '' );

if ( '' !== $card_radius && ctype_digit( $card_radius ) ) {
$rules[] = '--wa-card-radius:' . (int) $card_radius . 'px;';
}

if ( empty( $rules ) ) {
return '';
if ( '' !== $btn_radius && ctype_digit( $btn_radius ) ) {
$rules[] = '--wa-btn-radius:' . (int) $btn_radius . 'px;';
}

return '<style>#workos-authkit-root{' . implode( ' ', $rules ) . '}</style>';
// Page background targets the takeover body, not the root widget.
$body_rules = [];
if ( $valid_hex( $page_bg ) ) {
$body_rules[] = 'background:' . $page_bg . ';';
}

if ( $valid_hex( $card_bg ) ) {
$rules[] = '--wa-surface:' . $card_bg . ';';
}

if ( $valid_hex( $card_bdr ) ) {
$rules[] = '--wa-card-border:' . $card_bdr . ';';
}

if ( $valid_hex( $heading_clr ) ) {
$rules[] = '--wa-heading-color:' . $heading_clr . ';';
}

if ( $valid_hex( $sub_clr ) ) {
$rules[] = '--wa-subheading-color:' . $sub_clr . ';';
}

if ( $valid_hex( $btn_bg ) ) {
// A custom button color drops the matching WP-blue hover; use the
// same value for hover (flat) rather than deriving a darker shade.
$rules[] = '--wa-primary:' . $btn_bg . ';';
$rules[] = '--wa-primary-hover:' . $btn_bg . ';';
}

if ( $valid_hex( $btn_text ) ) {
$rules[] = '--wa-primary-fg:' . $btn_text . ';';
}

if ( $valid_hex( $sec_btn_bg ) ) {
$rules[] = '--wa-secondary-bg:' . $sec_btn_bg . ';';
}

if ( $valid_hex( $sec_btn_text ) ) {
$rules[] = '--wa-secondary-fg:' . $sec_btn_text . ';';
}

if ( $valid_hex( $sec_btn_bdr ) ) {
$rules[] = '--wa-secondary-border:' . $sec_btn_bdr . ';';
}

if ( $valid_hex( $links ) ) {
$rules[] = '--wa-links:' . $links . ';';
}

$out = '';
if ( ! empty( $body_rules ) ) {
$out .= '<style>body.workos-authkit-body{' . implode( ' ', $body_rules ) . '}</style>';
}
if ( ! empty( $rules ) ) {
$out .= '<style>#workos-authkit-root{' . implode( ' ', $rules ) . '}</style>';
}

return $out;
}

/**
Expand Down
Loading