feat: Sprint 2 — WordPress plugin (auth/api/site-info/admin) + web SiteConnector
Mirror to GitHub / mirror (push) Canceled after 0s

- plugin: Wursor_Auth (token hashing, HMAC, scoped tokens), Wursor_API (REST + auth), Wursor_Site_Info (builder/capabilities/preflight), Wursor_Admin
- plugin: test-auth.php (10 auth tests, run in a WP+PHP env)
- web: SiteConnector pairing UI (code + poll + states)
- api: PluginClient signs full REST route (matches WP get_route)
- 92 unit tests green
This commit is contained in:
SinachPat
2026-08-15 23:17:16 +01:00
parent 9801b9475b
commit 69b2481299
12 changed files with 515 additions and 4 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ describe('PluginClient', () => {
const [, init] = fetchMock.mock.calls[0] as [string, { headers: Record<string, string> }];
const ts = init.headers['X-Wursor-Timestamp'];
expect(init.headers['X-Wursor-Signature']).toBe(expectedSignature('h-secret', ts, 'POST', '/files', '{"a":1}'));
expect(init.headers['X-Wursor-Signature']).toBe(expectedSignature('h-secret', ts, 'POST', '/wursor/v1/files', '{"a":1}'));
});
it('maps a 401 to Authentication failed', async () => {
+9 -3
View File
@@ -14,12 +14,17 @@ function sha256hex(value: string): string {
export class PluginClient {
private readonly baseUrl: string;
private readonly namespace: string;
constructor(private readonly creds: PluginCredentials) {
constructor(
private readonly creds: PluginCredentials,
namespace = 'wursor/v1',
) {
if (!isHttpsUrl(creds.siteUrl)) {
throw new Error('Site URL must be https');
}
this.baseUrl = `${creds.siteUrl.replace(/\/$/, '')}/wp-json/wursor/v1`;
this.namespace = namespace;
this.baseUrl = `${creds.siteUrl.replace(/\/$/, '')}/wp-json/${namespace}`;
}
async get(path: string): Promise<unknown> {
@@ -32,8 +37,9 @@ export class PluginClient {
private async request(method: string, path: string, body?: unknown): Promise<unknown> {
const bodyText = body === undefined ? '' : JSON.stringify(body);
const route = `/${this.namespace}${path}`;
const timestamp = String(Math.floor(Date.now() / 1000));
const canonical = `${timestamp}\n${method}\n${path}\n${sha256hex(bodyText)}`;
const canonical = `${timestamp}\n${method}\n${route}\n${sha256hex(bodyText)}`;
const signature = createHmac('sha256', this.creds.hmacSecret).update(canonical).digest('hex');
const token = method === 'GET' ? this.creds.readToken : (this.creds.deployToken ?? this.creds.readToken);