Show the person's 2FA code on the firm site the extension is on
On a firm site, the extension draws a small corner chip with this machine's person's authenticator code for that site, a countdown, and a Copy button. It can be minimised to a "2FA" pill for the tab's session. - /api/autobuyer/otp?host= relays to the master's /api/otp?person=<instance_id>&host=, which matches the host to a firm's Site link and the person's assigned codes. No master link, a master that can't be reached, or no match all come back as no codes. An answer without scope: 'person' is refused: an older master ignores ?person= and would return every stored code. - The chip lives in a shadow root, counts down on its own, refetches when a code expires, and stays hidden for a minute after the runner last located something, so automated clicks never land on it. Copy uses execCommand inside the click, since the async Clipboard API can stall on a permission prompt, and leaves focus in the site's code field. - Apex (*.apextraderfunding.com) joins host_permissions, which also brings it into capture like the other firm sites. Version 0.16.0. - CatalogFirm mirrors the master's new optional siteUrl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
58e7414849
commit
d6bb5a7221
@@ -0,0 +1,35 @@
|
||||
import { getSetting } from '@/lib/db';
|
||||
import { corsJson, corsPreflight } from '../cors';
|
||||
|
||||
/** The extension's 2FA lookup. It knows the tab's host; this dashboard knows
|
||||
* which person it is (the master-issued `instance_id`) and where the master
|
||||
* is, so it asks the master for this person's codes on that site and passes
|
||||
* them through.
|
||||
*
|
||||
* Never an error status: no link to the master, or a master that can't be
|
||||
* reached, is just "no codes" with a reason — the extension shows nothing. */
|
||||
export async function GET(request: Request) {
|
||||
const host = new URL(request.url).searchParams.get('host')?.trim().toLowerCase();
|
||||
if (!host) return corsJson({ codes: [], reason: 'host is required' });
|
||||
|
||||
const master = getSetting('master_dashboard_url')?.trim().replace(/\/+$/, '');
|
||||
const person = getSetting('instance_id')?.trim();
|
||||
if (!master || !person) return corsJson({ codes: [], reason: 'not linked to master' });
|
||||
|
||||
const url = `${master}/api/otp?person=${encodeURIComponent(person)}&host=${encodeURIComponent(host)}`;
|
||||
try {
|
||||
const res = await fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(5000) });
|
||||
if (!res.ok) return corsJson({ codes: [], reason: `master answered ${res.status}` });
|
||||
const data = (await res.json()) as { codes?: unknown[]; serverTime?: number; scope?: string };
|
||||
// A master from before per-person lookups ignores ?person= and answers
|
||||
// with every stored code — never show those.
|
||||
if (data.scope !== 'person') return corsJson({ codes: [], reason: 'master is too old for 2FA lookups' });
|
||||
return corsJson({ codes: data.codes ?? [], serverTime: data.serverTime ?? Date.now() });
|
||||
} catch (err) {
|
||||
return corsJson({ codes: [], reason: `master unreachable: ${(err as Error).message}` });
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return corsPreflight();
|
||||
}
|
||||
Reference in New Issue
Block a user