优化了一些问题
This commit is contained in:
@@ -173,6 +173,12 @@ test('shared renderer contracts every configured authentication state', () => {
|
||||
assert.match(client, /aksrtAuthAutoUntil/);
|
||||
assert.match(client, /data-password-show/);
|
||||
assert.match(client, /pollQr/);
|
||||
assert.match(client, /AbortController/);
|
||||
assert.match(client, /refreshInFlight|inFlight/);
|
||||
assert.match(client, /visibilitychange/);
|
||||
assert.match(client, /Math\.pow\(2/);
|
||||
assert.match(client, /QR_TIMEOUT/);
|
||||
assert.match(client, /stopQr\(root\)/);
|
||||
assert.match(client, /window\.notyf\(escapeHtml\(text\), type \|\| 'info'\)/);
|
||||
assert.match(client, /notify\('请先完成人机验证', 'warning'\)/);
|
||||
assert.match(client, /form\.noValidate = true/);
|
||||
@@ -182,3 +188,47 @@ test('shared renderer contracts every configured authentication state', () => {
|
||||
assert.match(settings, /invitation_mode/);
|
||||
assert.match(settings, /\['close', 'open', 'must'\]/);
|
||||
});
|
||||
|
||||
test('OAuth frontend is accessible, redirect-aware, and removes retired providers', () => {
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const modal = read('templates/parts/modal-auth.php');
|
||||
const client = read('assets/js/auth.js');
|
||||
const userCenter = read('assets/js/routes/user-center.js');
|
||||
const bindings = read('inc/helpers/user-center-renderers/settings-bindings.php');
|
||||
const signCss = read('assets/css/routes/sign.css');
|
||||
const mainCss = read('assets/css/main.css');
|
||||
|
||||
assert.match(modal, /role="dialog"[^>]*aria-modal="true"[^>]*aria-labelledby="aksrt-auth-modal-title"/);
|
||||
assert.match(modal, /id="aksrt-auth-modal-title"/);
|
||||
assert.match(client, /focusableModalItems/);
|
||||
assert.match(client, /lastModalTrigger/);
|
||||
assert.match(client, /restore\.focus\(\)/);
|
||||
assert.match(shared, /role="tabpanel"[^>]*data-auth-panel="signin"/);
|
||||
assert.match(shared, /role="tabpanel"[^>]*data-auth-panel="oauth"/);
|
||||
assert.match(client, /setAttribute\('role', 'tab'\)/);
|
||||
assert.match(client, /aria-controls/);
|
||||
assert.match(client, /aria-selected/);
|
||||
assert.match(shared, /data-password-show[^>]*aria-label=/);
|
||||
assert.match(shared, /aria-pressed="false"/);
|
||||
assert.match(client, /setAttribute\('aria-label', visible \? '隐藏密码' : '显示密码'\)/);
|
||||
assert.match(client, /setAttribute\('aria-pressed', visible \? 'true' : 'false'\)/);
|
||||
assert.match(shared, /data-auth-qr-status[^>]*role="status"[^>]*aria-live="polite"/);
|
||||
assert.match(shared, /data-provider=/);
|
||||
assert.match(shared, /aria-label="<\?php echo esc_attr\(sprintf/);
|
||||
assert.match(client, /searchParams\.set\('redirect_to', redirect\)/);
|
||||
|
||||
assert.match(client, /oauth_action/);
|
||||
assert.match(client, /oauth_provider/);
|
||||
assert.match(client, /oauth_error/);
|
||||
assert.match(client, /oauthErrorMessages/);
|
||||
assert.doesNotMatch(client, /notify\([^\n;]*searchParams\.get\('oauth_error'/);
|
||||
assert.match(userCenter, /errorMessages\[currentUrl\.searchParams\.get\('oauth_error'\)/);
|
||||
assert.match(userCenter, /result\.status === 409/);
|
||||
assert.match(userCenter, /d\.message \|\| \(d\.data && d\.data\.message\)/);
|
||||
assert.match(bindings, /\$binding\['provider'\] \?\? \$binding\['key'\]/);
|
||||
assert.match(bindings, /\$binding\['start_url'\] \?\? \$binding\['url'\]/);
|
||||
assert.match(bindings, /data-provider=/);
|
||||
|
||||
assert.doesNotMatch(signCss + mainCss, /social-login-item[^\n{]*(?:baidu|alipay)|social-login-item\.(?:baidu|alipay)/i);
|
||||
assert.match(mainCss, /\.pay-box \.but\.baidu/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const test = require('node:test')
|
||||
const vm = require('node:vm')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..')
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
const walkFiles = (dir, extensions) => {
|
||||
const result = []
|
||||
for (const entry of fs.readdirSync(path.join(root, dir), { withFileTypes: true })) {
|
||||
const relative = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) result.push(...walkFiles(relative, extensions))
|
||||
else if (extensions.some((ext) => entry.name.endsWith(ext))) result.push(relative)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const oauthScope = [
|
||||
'src/Auth/OAuth.php',
|
||||
'src/Auth/OAuthProviderCatalog.php',
|
||||
'src/Auth/OAuthProviderBroker.php',
|
||||
'src/Auth/OAuthAccountService.php',
|
||||
'src/Auth/OAuthProxyClient.php',
|
||||
'src/Auth/OAuthProxyServer.php',
|
||||
'src/Auth/CLoginClient.php',
|
||||
'src/Admin/Schema/SchemaUsers.php',
|
||||
'assets/admin/src/schema/sections/users/oauth.js',
|
||||
].map(read).join('\n')
|
||||
|
||||
test('OAuth PHP behavior harness passes', () => {
|
||||
const output = execFileSync('php', [path.join(root, 'tests/widget-system/oauth-security-harness.php')], { encoding: 'utf8' })
|
||||
assert.match(output, /OAuth security harness passed/)
|
||||
})
|
||||
test('Baidu and Alipay are absent from the OAuth surface', () => {
|
||||
for (const needle of [
|
||||
'oauth_baidu_s',
|
||||
'oauth_alipay_s',
|
||||
'get_baidu_user_data',
|
||||
'get_alipay_user_data',
|
||||
'OAuthLogin\\Baidu',
|
||||
'OAuthLogin\\Alipay',
|
||||
"'baidu' => [",
|
||||
"'alipay' => [",
|
||||
]) {
|
||||
assert.equal(oauthScope.includes(needle), false, `unexpected OAuth reference: ${needle}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('Alipay payment remains available', () => {
|
||||
const functions = read('functions.php')
|
||||
const paymentSchema = read('src/Admin/Schema/SchemaPayment.php')
|
||||
const gateway = read('src/Payment/Gateways/AlipayGateway.php')
|
||||
assert.match(functions, /AlipayGateway/)
|
||||
assert.match(paymentSchema, /alipay_appid/)
|
||||
assert.match(paymentSchema, /alipay_privatekey/)
|
||||
assert.match(gateway, /class AlipayGateway/)
|
||||
})
|
||||
|
||||
test('OAuth uses one-time transactions and no PHP sessions', () => {
|
||||
const auth = fs.readdirSync(path.join(root, 'src', 'Auth'))
|
||||
.filter((name) => name.endsWith('.php'))
|
||||
.map((name) => read(path.join('src', 'Auth', name)))
|
||||
.join('\n')
|
||||
assert.match(auth, /OAuthTransactionStore::issue/)
|
||||
assert.match(auth, /OAuthTransactionStore::claim/)
|
||||
assert.equal(auth.includes('session_start('), false)
|
||||
assert.equal(auth.includes('$_SESSION'), false)
|
||||
})
|
||||
|
||||
test('provider catalog is the canonical provider list', () => {
|
||||
const catalog = read('src/Auth/OAuthProviderCatalog.php')
|
||||
for (const provider of ['github', 'gitee', 'qq', 'google', 'weibo', 'weixin']) {
|
||||
assert.match(catalog, new RegExp(`'${provider}'\\s*=>`))
|
||||
}
|
||||
assert.match(catalog, /CLOGIN_TYPES = \['qq', 'weixin', 'weibo'\]/)
|
||||
assert.match(catalog, /oauth_s/)
|
||||
assert.match(catalog, /oauth_bind_type/)
|
||||
})
|
||||
|
||||
test('verified email is required for email auto-linking', () => {
|
||||
const account = read('src/Auth/OAuthAccountService.php')
|
||||
const broker = read('src/Auth/OAuthProviderBroker.php')
|
||||
assert.match(account, /!empty\(\$identity\['email_verified'\]\)/)
|
||||
assert.match(broker, /primary.*verified/s)
|
||||
assert.match(broker, /email_verified.*Google|Google.*email_verified/s)
|
||||
})
|
||||
|
||||
test('OAuth secrets are marked and masked by the schema provider', () => {
|
||||
const phpSchema = read('src/Admin/Schema/SchemaUsers.php')
|
||||
const jsSchema = read('assets/admin/src/schema/sections/users/oauth.js')
|
||||
const provider = read('src/Admin/SchemaProvider.php')
|
||||
for (const key of [
|
||||
'oauth_proxy_secret',
|
||||
'github_appkey',
|
||||
'gitee_appkey',
|
||||
'qq_appkey',
|
||||
'google_client_secret',
|
||||
'weibo_appkey',
|
||||
'weixin_appsecret',
|
||||
'weixingzh_token',
|
||||
'weixingzh_appsecret',
|
||||
'clogin_key',
|
||||
]) {
|
||||
assert.match(phpSchema, new RegExp(`${key}[^\\n]+secret`))
|
||||
assert.match(jsSchema, new RegExp(`${key}[^\\n]+secret`))
|
||||
}
|
||||
assert.match(provider, /\*\*\*/)
|
||||
assert.match(provider, /!empty\(\$field\['secret'\]\)/)
|
||||
})
|
||||
|
||||
test('PHP and JavaScript OAuth schema field keys stay in parity', () => {
|
||||
const php = read('src/Admin/Schema/SchemaUsers.php')
|
||||
const js = read('assets/admin/src/schema/sections/users/oauth.js')
|
||||
const start = php.indexOf("'oauth' => [")
|
||||
const phpSection = php.slice(start, php.indexOf("'sms' => [", start))
|
||||
const phpKeys = [...phpSection.matchAll(/\['key' => '([^']+)', 'label'/g)].map((match) => match[1])
|
||||
const jsKeys = [...js.matchAll(/\{ key: '([^']+)', label:/g)].map((match) => match[1])
|
||||
assert.deepEqual([...new Set(phpKeys)].sort(), [...new Set(jsKeys)].sort())
|
||||
})
|
||||
test('OAuth result handler maps safe errors and preserves unrelated URL state', () => {
|
||||
const notices = []
|
||||
let replaced = ''
|
||||
const listeners = {}
|
||||
const location = new URL('https://site.test/sign/?oauth_action=error&oauth_provider=github&oauth_error=%3Cscript%3E&keep=1#section')
|
||||
const documentElement = { dataset: {}, contains: () => true }
|
||||
const document = {
|
||||
readyState: 'complete',
|
||||
documentElement,
|
||||
visibilityState: 'visible',
|
||||
querySelectorAll: () => [],
|
||||
querySelector: () => null,
|
||||
addEventListener: (name, fn) => { listeners[name] = fn },
|
||||
activeElement: null,
|
||||
body: { classList: { add() {}, remove() {} } },
|
||||
}
|
||||
const window = {
|
||||
aksrtData: {},
|
||||
URL,
|
||||
location,
|
||||
history: { replaceState: (_state, _title, value) => { replaced = value } },
|
||||
notyf: (message, type) => notices.push({ message, type }),
|
||||
matchMedia: () => ({ matches: false }),
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
localStorage: { getItem: () => null, setItem() {} },
|
||||
}
|
||||
vm.runInNewContext(read('assets/js/auth.js'), {
|
||||
window,
|
||||
document,
|
||||
URL,
|
||||
FormData: class {},
|
||||
fetch: () => Promise.reject(new Error('unexpected fetch')),
|
||||
console,
|
||||
CustomEvent: class {},
|
||||
AbortController,
|
||||
Date,
|
||||
Math,
|
||||
Promise,
|
||||
})
|
||||
assert.deepEqual(notices, [{ message: '第三方授权失败,请重试', type: 'danger' }])
|
||||
assert.equal(replaced, '/sign/?keep=1#section')
|
||||
assert.equal(replaced.includes('script'), false)
|
||||
})
|
||||
test('obsolete OAuth admin routes and components are removed', () => {
|
||||
const controller = read('src/Admin/UserController.php')
|
||||
const usersApi = read('assets/admin/src/api/modules/users.js')
|
||||
assert.equal(controller.includes("'/settings/oauth'"), false)
|
||||
assert.equal(controller.includes("'/settings/oauth-proxy'"), false)
|
||||
assert.equal(usersApi.includes('getOAuthSettings'), false)
|
||||
assert.equal(usersApi.includes('saveOAuthSettings'), false)
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/admin/src/components/OAuthProviderForm.vue')), false)
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/admin/src/pages/users/UsersSectionPage.vue')), false)
|
||||
})
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal executable harness for OAuth transaction, proxy, URL, and secret behavior.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace {
|
||||
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
define('MINUTE_IN_SECONDS', 60);
|
||||
define('MONTH_IN_SECONDS', 2592000);
|
||||
define('ARRAY_A', 'ARRAY_A');
|
||||
|
||||
$GLOBALS['wp_options'] = [];
|
||||
$GLOBALS['wp_cache'] = [];
|
||||
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function sanitize_key(string $value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($value)) ?? ''; }
|
||||
function sanitize_text_field(string $value): string { return trim(strip_tags($value)); }
|
||||
function esc_url_raw(string $value): string { return trim($value); }
|
||||
function wp_unslash($value) { return $value; }
|
||||
function wp_parse_url(string $url) { return parse_url($url); }
|
||||
function wp_generate_uuid4(): string { return '00000000-0000-4000-8000-' . substr(bin2hex(random_bytes(6)), 0, 12); }
|
||||
function wp_salt(string $scheme = 'auth'): string { return 'harness-salt-' . $scheme; }
|
||||
function is_ssl(): bool { return true; }
|
||||
function maybe_serialize($value): string { return serialize($value); }
|
||||
function maybe_unserialize($value) { $result = @unserialize((string) $value); return $result === false && $value !== serialize(false) ? $value : $result; }
|
||||
function wp_cache_delete(string $key, string $group = ''): void { unset($GLOBALS['wp_cache'][$group][$key]); }
|
||||
function get_option(string $key, $default = false) { return array_key_exists($key, $GLOBALS['wp_options']) ? $GLOBALS['wp_options'][$key] : $default; }
|
||||
function add_option(string $key, $value, string $deprecated = '', bool $autoload = true): bool {
|
||||
if (array_key_exists($key, $GLOBALS['wp_options'])) return false;
|
||||
$GLOBALS['wp_options'][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
function update_option(string $key, $value, $autoload = null): bool {
|
||||
$changed = !array_key_exists($key, $GLOBALS['wp_options']) || $GLOBALS['wp_options'][$key] !== $value;
|
||||
$GLOBALS['wp_options'][$key] = $value;
|
||||
return $changed;
|
||||
}
|
||||
function delete_option(string $key): bool {
|
||||
if (!array_key_exists($key, $GLOBALS['wp_options'])) return false;
|
||||
unset($GLOBALS['wp_options'][$key]);
|
||||
return true;
|
||||
}
|
||||
function get_template_directory_uri(): string { return 'https://example.com/theme'; }
|
||||
function get_theme_file_uri(string $path = ''): string { return 'https://example.com/theme/' . ltrim($path, '/'); }
|
||||
function get_theme_file_path(string $path = ''): string { return dirname(__DIR__, 2) . '/' . ltrim($path, '/'); }
|
||||
function sanitize_email(string $value): string { return filter_var($value, FILTER_VALIDATE_EMAIL) ? $value : ''; }
|
||||
function esc_html__(string $value, string $domain = ''): string { return $value; }
|
||||
|
||||
final class HarnessWpdb {
|
||||
public string $options = 'wp_options';
|
||||
public function prepare(string $query, ...$args): array { return [$query, $args]; }
|
||||
public function esc_like(string $value): string { return $value; }
|
||||
public function query($prepared): int {
|
||||
[$query, $args] = $prepared;
|
||||
if (str_starts_with(ltrim($query), 'DELETE')) {
|
||||
[$key, $serialized] = $args;
|
||||
if (!array_key_exists($key, $GLOBALS['wp_options']) || serialize($GLOBALS['wp_options'][$key]) !== $serialized) return 0;
|
||||
unset($GLOBALS['wp_options'][$key]);
|
||||
return 1;
|
||||
}
|
||||
if (str_starts_with(ltrim($query), 'UPDATE')) {
|
||||
[$new, $key, $old] = $args;
|
||||
if (!array_key_exists($key, $GLOBALS['wp_options']) || serialize($GLOBALS['wp_options'][$key]) !== $old) return 0;
|
||||
$GLOBALS['wp_options'][$key] = unserialize($new);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public function get_results($prepared, $format): array { return []; }
|
||||
}
|
||||
$GLOBALS['wpdb'] = new HarnessWpdb();
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
final class Settings {
|
||||
public static function flush_settings_cache(): void {}
|
||||
public static function sync_native_site_icon(string $value): void {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require dirname(__DIR__, 2) . '/src/Auth/OAuthTransactionStore.php';
|
||||
require dirname(__DIR__, 2) . '/src/Auth/OAuthUrlPolicy.php';
|
||||
require dirname(__DIR__, 2) . '/src/Auth/OAuthProxyProtocol.php';
|
||||
require dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaUsers.php';
|
||||
require dirname(__DIR__, 2) . '/src/Admin/SchemaProvider.php';
|
||||
|
||||
function check(bool $condition, string $message): void {
|
||||
if (!$condition) throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('A', 43);
|
||||
$token = \AKSRT\Auth\OAuthTransactionStore::issue('direct', ['provider' => 'github'], 60, true);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($token, 'direct', true) !== null, 'transaction peek failed');
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($token, 'other', true) === null, 'transaction kind mismatch accepted');
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('B', 43);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'wrong browser claimed transaction');
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('A', 43);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) !== null, 'valid browser failed claim');
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'transaction replay accepted');
|
||||
|
||||
$expired = \AKSRT\Auth\OAuthTransactionStore::issue('direct', [], 30, true);
|
||||
foreach ($GLOBALS['wp_options'] as $key => $row) {
|
||||
if (is_array($row) && ($row['kind'] ?? '') === 'direct') $GLOBALS['wp_options'][$key]['expires_at'] = time() - 1;
|
||||
}
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($expired, 'direct', true) === null, 'expired transaction accepted');
|
||||
|
||||
$request = [
|
||||
'v' => '2', 'request_id' => str_repeat('C', 43), 'provider' => 'github', 'action' => 'login',
|
||||
'agent_back_url' => 'https://example.com/oauth/proxy/callback/', 'ts' => (string) time(),
|
||||
];
|
||||
$secret = 'test-secret-value';
|
||||
$requestSign = \AKSRT\Auth\OAuthProxyProtocol::sign_request($request, $secret);
|
||||
check(\AKSRT\Auth\OAuthProxyProtocol::verify_request($request, $secret, $requestSign), 'request signature failed');
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::verify_callback($request, $secret, $requestSign), 'direction-separated HMAC failed');
|
||||
$tampered = $request; $tampered['provider'] = 'qq';
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::verify_request($tampered, $secret, $requestSign), 'tampered request accepted');
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::timestamp_valid((string) (time() - 1000)), 'expired timestamp accepted');
|
||||
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('http://example.com') === '', 'HTTP URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://user@example.com') === '', 'userinfo URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://127.0.0.1') === '', 'loopback URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://10.0.0.1') === '', 'private URL accepted');
|
||||
check(\AKSRT\Auth\OAuthProxyProtocol::callback_url('https://example.com/wrong') === '', 'wrong callback path accepted');
|
||||
|
||||
$provider = \AKSRT\Admin\SchemaProvider::get_instance();
|
||||
$base = $provider->get_defaults('users', 'oauth');
|
||||
$base['oauth_s'] = true;
|
||||
$base['oauth_weixingzh_s'] = true;
|
||||
$base['weixingzh_gzh_type'] = 'service';
|
||||
$base['weixingzh_appid'] = 'appid';
|
||||
$base['weixingzh_appsecret'] = 'secret';
|
||||
$base['weixingzh_token'] = '***';
|
||||
$GLOBALS['wp_options']['aksrt_settings'] = [];
|
||||
check(!$provider->validate('users', $base, 'oauth')['valid'], 'masked missing token treated as configured');
|
||||
$GLOBALS['wp_options']['aksrt_settings'] = ['weixingzh_token' => 'stored-token'];
|
||||
check($provider->validate('users', $base, 'oauth')['valid'], 'stored token not preserved by mask validation');
|
||||
|
||||
$provider->write_settings('users', ['weixingzh_token' => '***'], 'oauth');
|
||||
check($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] === 'stored-token', 'masked token overwrote stored secret');
|
||||
$provider->write_settings('users', ['weixingzh_token' => 'replacement'], 'oauth');
|
||||
check($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] === 'replacement', 'new token failed to replace secret');
|
||||
$provider->write_settings('users', ['__clear_secrets' => ['weixingzh_token']], 'oauth');
|
||||
check(!isset($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token']), 'explicit secret clear failed');
|
||||
$GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] = 'final-token';
|
||||
$clearData = $base;
|
||||
$clearData['weixingzh_token'] = '***';
|
||||
$clearData['__clear_secrets'] = ['weixingzh_token'];
|
||||
check(!$provider->validate('users', $clearData, 'oauth')['valid'], 'active provider allowed required secret clear');
|
||||
check($provider->read_settings('users', 'oauth')['weixingzh_token'] === '***', 'secret read was not masked');
|
||||
|
||||
echo "OAuth security harness passed.\n";
|
||||
}
|
||||
@@ -352,7 +352,7 @@ test('settings actions use rehydrated fixed-id notyf flows', () => {
|
||||
assert.match(userCenter, /notyf\([^;]+?'danger'[^;]+?toastId\);/);
|
||||
assert.match(userCenter, /register\('aksrt-settings',[\s\S]*?initSettings\(root\);[\s\S]*?initBindPhone\(root\);/);
|
||||
assert.match(userCenter, /aksrtSettingsInitialized/);
|
||||
assert.match(userCenter, /oauth_bound[\s\S]*?notyf\([^;]+?'success'\)[\s\S]*?history\.replaceState/);
|
||||
assert.match(userCenter, /oauth_action[\s\S]*?notyf\(message, type\)[\s\S]*?history\.replaceState/);
|
||||
assert.doesNotMatch(userCenter, /oauth_unbound/);
|
||||
|
||||
assert.match(userCenter, /'aksrt-settings-phone-code-toast'/);
|
||||
|
||||
Reference in New Issue
Block a user