mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* docs(plugins): add microsoft 365 calendar provider plan * fix(jira): gate Electron requests behind one-shot capability Claim privileged Jira IPC before plugin startup and return responses through invoke instead of a broadcast event. Keep arbitrary HTTP(S) Jira hosts supported while rejecting redirects and bounding request resources. * fix(jira): enforce Electron request capability Bind privileged Jira IPC to a main-issued renderer-document token and strip raw Electron events from renderer callbacks. Scope image authentication by origin, base path, and resource type while preserving safe redirects and legacy configurations. * fix(electron): handle payload-only IPC lifecycle Clear Jira image authentication before replacement and when a new renderer document claims the capability. Parse before-close IDs from payload-only events so pending sync and finish-day hooks can complete. * fix(electron): address Jira IPC capability review findings - electron.effects: read ANY_FILE_DOWNLOADED payload at [0] after the payload-only IPC refactor (was [1], now undefined -> TypeError on every download); guard against a malformed payload - jira-capability: rotate the token on re-register so a renderer reload that reuses the WebFrameMain object is not permanently locked out of Jira; invalidates any stale token - document that the one-shot consumption order, not the bypassable main-frame check, is the real capability boundary - jira-electron-bridge: skip the no-op clearImgHeaders IPC round-trip when image auth was never set up (non-Jira detail-panel open/close) - jira-api: route a synchronous _toElectronRequestInit throw through _handleResponse instead of leaking a dangling request-log entry * test(electron): cover ANY_FILE_DOWNLOADED payload parsing Extract parseDownloadedFilePayload from ElectronEffects and add a regression spec pinning the payload-only shape ([file], not [event, file]) that caused a TypeError on every download. Hardened against non-array input surfaced by the new test. * fix(electron): restore Node ambient globals for frontend build
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { randomBytes } from 'node:crypto';
|
|
import { JiraCapabilityEnvelope } from './shared-with-frontend/jira-request.model';
|
|
|
|
const TOKEN_BYTES = 32;
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
|
|
export class JiraCapabilityRegistry {
|
|
private readonly _tokens = new WeakMap<object, string>();
|
|
|
|
constructor(
|
|
private readonly _createToken: () => string = () =>
|
|
randomBytes(TOKEN_BYTES).toString('base64url'),
|
|
) {}
|
|
|
|
register(frame: object): string {
|
|
// Always issue a fresh token, even when this frame object already has one.
|
|
// A renderer reload re-runs the preload and re-registers; if Electron hands
|
|
// back the same WebFrameMain object, returning null would leave the new
|
|
// document permanently without a capability (Jira broken until a full app
|
|
// restart). Rotating the token also invalidates any stale token still held
|
|
// by the previous document.
|
|
const token = this._createToken();
|
|
this._tokens.set(frame, token);
|
|
return token;
|
|
}
|
|
|
|
isAuthorized(frame: object, token: unknown): token is string {
|
|
return typeof token === 'string' && this._tokens.get(frame) === token;
|
|
}
|
|
|
|
unwrap<T>(frame: object, envelope: unknown): T {
|
|
if (
|
|
!isRecord(envelope) ||
|
|
!this.isAuthorized(frame, envelope.capabilityToken) ||
|
|
!Object.prototype.hasOwnProperty.call(envelope, 'payload')
|
|
) {
|
|
throw new Error('Unauthorized Jira IPC request');
|
|
}
|
|
|
|
return (envelope as unknown as JiraCapabilityEnvelope<T>).payload;
|
|
}
|
|
}
|