| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import { fileURLToPath } from 'node:url';
- import { request, invoke, appId, functionsUrl } from '../server/scripts/platform-client.mjs';
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
- const OUT = path.join(ROOT, 'work/workshop-auth-recovery-20261108-r1');
- const PLAN = path.join(OUT, 'renewal-plan.json');
- const RECEIPT = path.join(OUT, 'server-renewal-receipt.json');
- const EXPECTED_ID = 'MqKRUv9JQc';
- const ORIGINAL_EXPIRY = '2026-11-06T10:42:23.873Z';
- const EXPECTED_SCOPES = ['source:write', 'ingest:write', 'data:read', 'report:ingest', 'report:publish', 'noise:write', 'noise:read'];
- const sha = value => crypto.createHash('sha256').update(value).digest('hex');
- const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
- const must = (value, code) => { if (!value) throw Object.assign(new Error(code), { code }); };
- const expiryOf = row => typeof row.expiresAt === 'string' ? row.expiresAt : row.expiresAt?.iso;
- const safeRow = row => ({ objectId: row.objectId, enabled: row.enabled, expiresAt: expiryOf(row), scopes: row.scopes, projectIds: row.projectIds, updatedAt: row.updatedAt });
- function readServiceConfig() {
- const config = {};
- for (const line of fs.readFileSync('F:/文档/康师傅数字人受限连接.env', 'utf8').split(/\r?\n/)) {
- const m = line.match(/^(KSF_SERVICE_TOKEN|KSF_FUNCTIONS_URL|KSF_PARSE_APP_ID)=(.*)$/);
- if (m) config[m[1]] = m[2].trim();
- }
- must(config.KSF_SERVICE_TOKEN && config.KSF_FUNCTIONS_URL === functionsUrl && config.KSF_PARSE_APP_ID === appId, 'SERVICE_CONFIG_MISMATCH');
- return config;
- }
- async function readCredential(tokenHash) {
- const q = new URLSearchParams({ where: JSON.stringify({ tokenHash }), limit: '2' });
- const rows = (await request('GET', '/classes/KsfServiceCredential?' + q)).results;
- must(rows?.length === 1 && rows[0].objectId === EXPECTED_ID, 'CREDENTIAL_IDENTITY_MISMATCH');
- const row = rows[0];
- must(row.enabled === true && row.tokenHash === tokenHash, 'CREDENTIAL_NOT_ENABLED');
- must(equal(row.scopes, EXPECTED_SCOPES) && equal(row.projectIds, ['*']), 'CREDENTIAL_SCOPE_CHANGED');
- return row;
- }
- async function run() {
- must(process.argv.length <= 3 && (!process.argv[2] || process.argv[2] === '--apply'), 'USAGE');
- fs.mkdirSync(OUT, { recursive: true });
- const config = readServiceConfig();
- const tokenHash = sha(config.KSF_SERVICE_TOKEN);
- const headers = { 'X-Parse-Application-Id': appId, 'X-Ksf-Service-Token': config.KSF_SERVICE_TOKEN, 'Content-Type': 'application/json' };
- const before = await readCredential(tokenHash);
- const existingReceipt = fs.existsSync(RECEIPT) ? JSON.parse(fs.readFileSync(RECEIPT, 'utf8')) : null;
- if (existingReceipt?.serverAuthorizationRestored && expiryOf(before) === existingReceipt.after.expiresAt) {
- console.log(JSON.stringify({ ok: true, alreadyApplied: true, receipt: RECEIPT, expiresAt: expiryOf(before), authorizationRecordWrites: 0 }));
- return;
- }
- must(expiryOf(before) === ORIGINAL_EXPIRY, 'UNKNOWN_CREDENTIAL_EXPIRY');
- const pre = await invoke('ksfGetIngestContract', { requestId: 'workshop-auth-before-20261108-r1' }, headers);
- must(pre.http === 401 && pre.body.result?.errors?.some(e => e.code === 'AUTH_REQUIRED'), 'EXPIRY_FAILURE_NOT_REPRODUCED');
- if (process.argv[2] !== '--apply') {
- const plan = {
- schema: 'ksf.workshop.authorization-renewal-plan.v1', plannedAt: new Date().toISOString(), dryRun: true,
- before: safeRow(before), beforeFingerprint: sha(JSON.stringify(before)),
- newExpiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(),
- fieldsToChange: ['expiresAt'], ownerIdentityPreserved: true, tokenPreserved: true, scopesPreserved: true,
- preProbe: { http: pre.http, errorCodes: pre.body.result.errors.map(e => e.code) },
- authorizationRecordWrites: 0, businessRecordWrites: 0, paidCalls: 0,
- };
- fs.writeFileSync(PLAN, JSON.stringify(plan, null, 2) + '\n');
- console.log(JSON.stringify(plan, null, 2));
- return;
- }
- must(fs.existsSync(PLAN), 'REVIEWABLE_PLAN_REQUIRED');
- const plan = JSON.parse(fs.readFileSync(PLAN, 'utf8'));
- must(plan.beforeFingerprint === sha(JSON.stringify(before)), 'CONCURRENT_CREDENTIAL_CHANGE');
- must(Date.now() - Date.parse(plan.plannedAt) < 3600000 && Date.parse(plan.newExpiresAt) > Date.now(), 'RENEWAL_PLAN_EXPIRED');
- const receipt = {
- schema: 'ksf.workshop.authorization-renewal-receipt.v1', startedAt: new Date().toISOString(),
- before: safeRow(before), requestedExpiresAt: plan.newExpiresAt, mutationAttempted: false,
- serverAuthorizationRestored: false, agentEncryptedEntryVerified: false, actualBusinessWriteVerified: false,
- authorizationRecordWrites: 0, businessRecordWrites: 0, paidCalls: 0,
- };
- const save = () => fs.writeFileSync(RECEIPT, JSON.stringify(receipt, null, 2) + '\n');
- save();
- try {
- receipt.mutationAttempted = true;
- save();
- await request('PUT', '/classes/KsfServiceCredential/' + EXPECTED_ID, { expiresAt: { __type: 'Date', iso: plan.newExpiresAt } });
- receipt.authorizationRecordWrites = 1;
- const after = await readCredential(tokenHash);
- receipt.after = safeRow(after);
- must(expiryOf(after) === plan.newExpiresAt, 'EXPIRY_READBACK_MISMATCH');
- receipt.ownerIdentityPreserved = after.objectId === before.objectId;
- receipt.tokenPreserved = after.tokenHash === before.tokenHash;
- receipt.scopesPreserved = equal(after.scopes, before.scopes) && equal(after.projectIds, before.projectIds);
- const contract = await invoke('ksfGetIngestContract', { requestId: 'workshop-auth-after-20261108-r1' }, headers);
- const data = contract.body.result?.data;
- must(contract.http === 200 && contract.body.result?.ok === true && data?.allowedClasses?.length > 0, 'RESTRICTED_CONTRACT_FAILED');
- receipt.contract = { http: contract.http, ok: true, schemaVersion: data.schemaVersion, contractRevision: data.contractRevision,
- allowedClassCount: data.allowedClasses.length, workshop: data.workshop,
- reportConfirmation: data.capabilities?.reportConfirmation, reportPublication: data.capabilities?.reportPublication };
- const dictionary = await invoke('ksfGetDataDictionary', { summary: true, requestId: 'workshop-auth-schema-20261108-r1' }, headers);
- must(dictionary.http === 200 && dictionary.body.result?.ok === true, 'RESTRICTED_SCHEMA_FAILED');
- receipt.schemaProbe = { http: dictionary.http, ok: true, tableCount: dictionary.body.result.data?.tableCount, fieldCount: dictionary.body.result.data?.fieldCount };
- const search = await invoke('ksfSearchRecords', { className: 'KfArtifact', filters: { dataMode: 'manual' }, limit: 1, requestId: 'workshop-auth-read-20261108-r1' }, headers);
- must(search.http === 200 && search.body.result?.ok === true, 'BUSINESS_READ_FAILED');
- const searchData = search.body.result.data;
- const rows = searchData.records || searchData.results || searchData.items || [];
- receipt.businessRead = { http: search.http, ok: true, requestedClass: 'KfArtifact', dataMode: 'manual', returnedRows: rows.length };
- if (rows[0]?.objectId) {
- const read = await invoke('ksfGetRecord', { className: 'KfArtifact', objectId: rows[0].objectId, requestId: 'workshop-auth-exact-read-20261108-r1' }, headers);
- must(read.http === 200 && read.body.result?.ok === true && read.body.result.data?.record?.objectId === rows[0].objectId, 'EXACT_READ_FAILED');
- receipt.exactRead = { http: read.http, ok: true, className: 'KfArtifact', objectId: rows[0].objectId };
- }
- receipt.serverAuthorizationRestored = true;
- receipt.managementMachineRestrictedTokenVerified = true;
- receipt.finishedAt = new Date().toISOString();
- save();
- console.log(JSON.stringify(receipt, null, 2));
- } catch (error) {
- receipt.error = { code: /^[A-Z][A-Z0-9_]+$/.test(error.code || '') ? error.code : 'RECOVERY_CHECK_FAILED' };
- receipt.stoppedAt = new Date().toISOString();
- save();
- throw error;
- } finally {
- delete config.KSF_SERVICE_TOKEN;
- }
- }
- run().catch(error => {
- console.error(JSON.stringify({ ok: false, code: /^[A-Z][A-Z0-9_]+$/.test(error.code || '') ? error.code : 'AUTH_RECOVERY_STOPPED', message: '授权修复停止;核对脱敏回执,不输出配置或原始异常。' }));
- process.exitCode = 1;
- });
|