@qrauth/web-components 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +20 -5
- package/dist/base.d.ts +35 -0
- package/dist/login.d.ts +16 -2
- package/dist/qrauth-components.esm.js +89 -79
- package/dist/qrauth-components.js +89 -79
- package/dist/twofa.d.ts +6 -0
- package/package.json +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @qrauth/web-components
|
|
2
2
|
|
|
3
|
+
## [0.4.2] — 2026-10-13
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
- **Server error text is rendered as text.** All three components put the API's error message into the page with `textContent`. Before, it was interpolated into a template assigned to `innerHTML`, so markup in a message (the API used to echo the `redirect-uri` value back) was parsed in the embedding site's origin.
|
|
7
|
+
- **`<qrauth-login>` and `<qrauth-2fa>` send the PKCE verifier in the `X-QRAuth-Code-Verifier` header** when polling, not in the URL. A query string is written to proxy and server access logs. Requires QRAuth API ≥ 2026-10-13. Against an older API the header is ignored and the approved result comes back without the user; the components now report that as `qrauth:error` instead of a sign-in with no identity.
|
|
8
|
+
- **`<qrauth-login redirect-url>` only follows http(s) and relative URLs.** Any other scheme (`javascript:`, `data:`, ...) is ignored with a console warning. This includes custom app schemes (`myapp://`), which were never documented but did work.
|
|
9
|
+
- **A throwing `on-auth` callback no longer stops the redirect or the modal from closing**, and only an own property of `window` is called (not an inherited one such as `constructor`).
|
|
10
|
+
|
|
11
|
+
### Docs
|
|
12
|
+
- `on-auth` is documented: it names a function on `window`, which is looked up and called with the session result. Set it only from your own markup. The `qrauth:authenticated` event carries the same data and needs no global.
|
|
13
|
+
|
|
14
|
+
### Notes
|
|
15
|
+
- No attribute or event changed. Sites that embed the unpinned `components.js` pick this up automatically; sites pinned to a versioned file with an SRI hash need the new file name and hash from `https://cdn.qrauth.io/v1/latest.json`.
|
|
16
|
+
- If your site sets a Content-Security-Policy, `style-src` must allow `'unsafe-inline'` for the components' shadow-DOM styles.
|
|
17
|
+
- Bundle size: 20.8 KB gzipped IIFE / 20.8 KB gzipped ESM (0.4.1: 20.3 KB).
|
|
18
|
+
|
|
19
|
+
## [0.4.1] — 2026-04-29
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Removed dependency on `api.qrserver.com` for QR rendering. QR codes are now rendered locally as inline SVG via the `qrcode` library. Required for WP.org plugin directory submission and for any consumer with strict CSP.
|
|
23
|
+
- QR quiet zone increased from 1 to 4 modules per ISO/IEC 18004 — restores the scan reliability of the previous external renderer on stricter readers (iPhone Camera, older Android, low-light captures).
|
|
24
|
+
- Capacity overflow in `qrToSvg()` no longer crashes the component render — `QRCode.create` is now wrapped in try/catch and returns an empty SVG on overflow, letting the calling state render its empty-qr fallback.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- New runtime dependency: `qrcode@1.5.4` (already pinned by `@qrauth/animated-qr`).
|
|
28
|
+
- **`<qrauth-login>` and `<qrauth-2fa>` mobile "Continue with QRAuth" CTA** now appends `?dr=1` to the hosted approval URL. Forward-compatible with an upcoming server-side change at `qrauth.io/a/:token` that will gate the post-approval redirect on this flag — same-device clicks redirect, cross-device scans land on a terminal "Approved, you can close this tab" state. No behavior change until the server gating ships; safe to deploy independently.
|
|
29
|
+
|
|
3
30
|
## 0.4.0
|
|
4
31
|
|
|
5
32
|
- **`<qrauth-login>` and `<qrauth-2fa>`: new `redirect-uri` attribute.** After the user taps Approve on the mobile CTA path, the QRAuth hosted approval page now navigates back to the consumer site instead of landing on a "you can close this page" dead end. The attribute is forwarded to `POST /api/v1/auth-sessions` as `redirectUrl` and validated server-side against the app's registered `redirectUrls` allowlist (same security model as OAuth 2.0 redirect URIs — strips trailing slash and ignores query + fragment before comparison). No-op on the desktop QR flow since polling in the original tab already handles the transition.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Drop-in Web Components for [QRAuth](https://qrauth.io) — passwordless QR authentication, 2FA, and ephemeral delegated access. Shadow DOM, framework-free, ~10 KB gzipped.
|
|
4
4
|
|
|
5
|
+
<a href="https://openid.net/certification/certified-openid-providers-profiles/"><img src="https://qrauth.io/assets/certifications/openid-certified.png" alt="OpenID Certified" width="180" /></a>
|
|
6
|
+
|
|
7
|
+
QRAuth is an **OpenID Certified™** OpenID Provider (Basic OP + Config OP profiles).
|
|
8
|
+
|
|
5
9
|
- `<qrauth-login>` — passwordless sign-in, button or inline. Mobile-aware pending state (tap-to-continue CTA with QR demoted under a "Use another device" expander).
|
|
6
10
|
- `<qrauth-2fa>` — drop-in second-factor step with session-token binding.
|
|
7
11
|
- `<qrauth-ephemeral>` — time-limited, device-bound access QRs.
|
|
@@ -14,12 +18,12 @@ All three components bubble `qrauth:*` events across the Shadow DOM boundary (`c
|
|
|
14
18
|
npm install @qrauth/web-components
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
Or load the CDN IIFE directly in any HTML page:
|
|
21
|
+
Or load the CDN IIFE directly in any HTML page (latest hashes at [`https://cdn.qrauth.io/v1/latest.json`](https://cdn.qrauth.io/v1/latest.json)):
|
|
18
22
|
|
|
19
23
|
```html
|
|
20
24
|
<script
|
|
21
|
-
src="https://cdn.qrauth.io/v1/components
|
|
22
|
-
integrity="sha384
|
|
25
|
+
src="https://cdn.qrauth.io/v1/components-<version>.js"
|
|
26
|
+
integrity="<sha384 for that file, from latest.json>"
|
|
23
27
|
crossorigin="anonymous"
|
|
24
28
|
></script>
|
|
25
29
|
|
|
@@ -51,12 +55,13 @@ Or load the CDN IIFE directly in any HTML page:
|
|
|
51
55
|
| `theme` | `light` \| `dark` | `light` | Colour scheme. |
|
|
52
56
|
| `base-url` | URL | `https://qrauth.io` | API host. Override for staging / local dev. |
|
|
53
57
|
| `scopes` | space-separated | `identity` | OAuth-style scopes requested. |
|
|
54
|
-
| `redirect-url` | URL | — | Auto-redirect after success. |
|
|
55
|
-
| `on-auth` | string | — | Name of a
|
|
58
|
+
| `redirect-url` | URL | — | Auto-redirect after success. Only an http(s) URL or a relative URL is followed; any other scheme is ignored with a console warning. |
|
|
59
|
+
| `on-auth` | string | — | Name of a function on `window`, called with the session result on success. It is looked up by name, so set it only from your own markup, never from user input. The `qrauth:authenticated` event carries the same data and needs no global. |
|
|
56
60
|
| `display` | `button` \| `inline` | `button` | Modal trigger vs. embedded widget. |
|
|
57
61
|
| `animated` | boolean | off | Enables the pulse animation on the QR. |
|
|
58
62
|
| `force-mode` | `mobile` \| `desktop` \| `auto` | `auto` | Override automatic mobile detection. |
|
|
59
63
|
| `mobile-fallback-only` | boolean | off | Keep the QR-first body on every device; disables the mobile CTA path. |
|
|
64
|
+
| `redirect-uri` | URL | — | Where the hosted approval page sends the user after Approve. Required for the mobile flow to feel complete. Must match the app's registered `redirectUrls` allowlist. See [docs.qrauth.io/guide/web-components](https://docs.qrauth.io/guide/web-components#the-mobile-flow) for the URL-callback handler your app needs. |
|
|
60
65
|
|
|
61
66
|
## Events
|
|
62
67
|
|
|
@@ -68,6 +73,9 @@ All events bubble across the Shadow DOM (`composed: true`):
|
|
|
68
73
|
- `qrauth:denied` — user denied approval on their device
|
|
69
74
|
- `qrauth:error` — `detail: { message }`
|
|
70
75
|
|
|
76
|
+
`qrauth:error` carries `detail.message`, which can be text from the API. Show
|
|
77
|
+
it with `textContent`, never `innerHTML`.
|
|
78
|
+
|
|
71
79
|
## Theming
|
|
72
80
|
|
|
73
81
|
Override any of the following CSS custom properties on the host page or on the element itself:
|
|
@@ -89,6 +97,13 @@ qrauth-login {
|
|
|
89
97
|
|
|
90
98
|
Or flip the bundled dark palette via `theme="dark"`.
|
|
91
99
|
|
|
100
|
+
## Content Security Policy
|
|
101
|
+
|
|
102
|
+
The components style themselves inside their shadow root with inline
|
|
103
|
+
`<style>`. If the embedding page sends a Content-Security-Policy, `style-src`
|
|
104
|
+
must allow `'unsafe-inline'`. `connect-src` must allow the QRAuth API origin
|
|
105
|
+
(`https://qrauth.io` unless you set `base-url`).
|
|
106
|
+
|
|
92
107
|
## Framework integration
|
|
93
108
|
|
|
94
109
|
The components are framework-free native Custom Elements. They work in React, Vue, Svelte, Angular, plain HTML — wherever the browser exposes `customElements`. For React, wrap event listeners in `useEffect` because React's synthetic event system doesn't observe custom events directly.
|
package/dist/base.d.ts
CHANGED
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
* Exported for documentation and test mocks.
|
|
9
9
|
*/
|
|
10
10
|
export declare const MOBILE_MEDIA_QUERY = "(pointer: coarse) and (hover: none)";
|
|
11
|
+
/** Header the API reads the PKCE verifier from when an auth session is polled. */
|
|
12
|
+
export declare const CODE_VERIFIER_HEADER = "X-QRAuth-Code-Verifier";
|
|
13
|
+
/**
|
|
14
|
+
* Where `redirect-url` may send the browser: an http(s) URL, or a relative
|
|
15
|
+
* one resolved against the current page. Anything that does not resolve to
|
|
16
|
+
* http(s), `javascript:` and `data:` in particular, is refused. Returns the
|
|
17
|
+
* absolute URL to navigate to, or null. This is a scheme check, not an
|
|
18
|
+
* origin check: an absolute URL on another site is allowed, as documented.
|
|
19
|
+
*/
|
|
20
|
+
export declare function safeRedirectTarget(raw: string | null | undefined, pageUrl: string): string | null;
|
|
11
21
|
export declare abstract class QRAuthElement extends HTMLElement {
|
|
12
22
|
protected shadow: ShadowRoot;
|
|
13
23
|
private _sseConnection;
|
|
@@ -43,6 +53,31 @@ export declare abstract class QRAuthElement extends HTMLElement {
|
|
|
43
53
|
protected startPolling(url: string, intervalMs: number, callback: (data: unknown) => void, headers?: Record<string, string>): void;
|
|
44
54
|
protected stopPolling(): void;
|
|
45
55
|
protected cleanup(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Put a message into the rendered error state as text. The message can come
|
|
58
|
+
* from the API, so it is never parsed as HTML; the template only supplies
|
|
59
|
+
* the element and its default wording.
|
|
60
|
+
*/
|
|
61
|
+
protected setErrorText(message: string | null | undefined): void;
|
|
62
|
+
/**
|
|
63
|
+
* True when a PKCE-bound session came back APPROVED without the user: the
|
|
64
|
+
* API did not accept the verifier (an API older than the verifier header
|
|
65
|
+
* ignores it). There is no identity to hand to the page, so this must not
|
|
66
|
+
* be reported as a successful sign-in.
|
|
67
|
+
*/
|
|
68
|
+
protected isApprovalWithoutIdentity(sentVerifier: boolean, data: {
|
|
69
|
+
user?: unknown;
|
|
70
|
+
} | undefined): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Call the function named by an `on-*` attribute. Only an own property of
|
|
73
|
+
* `window` is called, not an inherited one (`constructor`, `toString`), and
|
|
74
|
+
* a throwing callback does not stop the component. Built-ins such as
|
|
75
|
+
* `alert` are own properties of `window` in browsers, so the attribute must
|
|
76
|
+
* still come from the embedding page's own markup, as documented.
|
|
77
|
+
*/
|
|
78
|
+
protected callGlobal(name: string | null, payload: unknown): void;
|
|
79
|
+
/** Leave the page. Separate so it can be observed in tests. */
|
|
80
|
+
protected navigate(url: string): void;
|
|
46
81
|
protected emit(name: string, detail?: unknown): void;
|
|
47
82
|
protected generateCodeVerifier(): Promise<string>;
|
|
48
83
|
protected computeCodeChallenge(verifier: string): Promise<string>;
|
package/dist/login.d.ts
CHANGED
|
@@ -7,8 +7,14 @@ import { QRAuthElement } from './base.js';
|
|
|
7
7
|
* theme — "light" (default) | "dark"
|
|
8
8
|
* base-url — API base URL (default: https://qrauth.io)
|
|
9
9
|
* scopes — Space-separated scopes (default: "identity")
|
|
10
|
-
* redirect-url — Optional redirect after successful auth
|
|
11
|
-
*
|
|
10
|
+
* redirect-url — Optional redirect after successful auth. Only an
|
|
11
|
+
* http(s) URL or a relative URL is followed; any
|
|
12
|
+
* other scheme is ignored with a console warning.
|
|
13
|
+
* on-auth — Name of a function on `window` to call on success
|
|
14
|
+
* with the session result. It is looked up by name
|
|
15
|
+
* and called, so set it only from your own markup,
|
|
16
|
+
* never from user input. The `qrauth:authenticated`
|
|
17
|
+
* event carries the same data and needs no global.
|
|
12
18
|
* display — "button" (default) | "inline"
|
|
13
19
|
* animated — If present, enables subtle QR pulse animation
|
|
14
20
|
* force-mode — "mobile" | "desktop" | "auto" (default "auto").
|
|
@@ -53,6 +59,7 @@ export declare class QRAuthLogin extends QRAuthElement {
|
|
|
53
59
|
private _codeVerifier;
|
|
54
60
|
private _status;
|
|
55
61
|
private _expiresAt;
|
|
62
|
+
private _currentQrUrl;
|
|
56
63
|
private _timerInterval;
|
|
57
64
|
private _pollTimeout;
|
|
58
65
|
get scopes(): string;
|
|
@@ -97,11 +104,18 @@ export declare class QRAuthLogin extends QRAuthElement {
|
|
|
97
104
|
* The existing polling loop in _beginPolling picks up SCANNED / APPROVED
|
|
98
105
|
* transitions from the server once the user completes the flow there,
|
|
99
106
|
* so qrauth:scanned and qrauth:authenticated fire unchanged.
|
|
107
|
+
*
|
|
108
|
+
* The `dr=1` query flag tells the hosted approval page that this is a
|
|
109
|
+
* same-device click (not a cross-device QR scan), and that the
|
|
110
|
+
* post-approval redirect to redirectUrl should fire. The QR matrix
|
|
111
|
+
* encodes the bare /a/:token URL, so a scanner cannot carry this flag.
|
|
100
112
|
*/
|
|
101
113
|
private _openApprovalPage;
|
|
102
114
|
private _openModal;
|
|
103
115
|
private _closeModal;
|
|
104
116
|
private _startAuth;
|
|
117
|
+
/** The scan was approved but the API returned no identity for it. */
|
|
118
|
+
private _failWithoutIdentity;
|
|
105
119
|
private _retry;
|
|
106
120
|
private _beginPolling;
|
|
107
121
|
private _handleStatusChange;
|