@volter/twin-xidentity 0.1.1 → 0.1.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.
@@ -10,8 +10,9 @@
10
10
  // • it serves the authorize page's own assets under the twin-namespaced `/_twin/assets/*`.
11
11
  //
12
12
  // It also tells the handler its OWN origin, so the consent form's action points BACK AT THE TWIN.
13
- import { serveHttp } from '@volter/world-core';
13
+ import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
14
14
  import { CONSENT_CLIENT_CSS, CONSENT_CLIENT_JS, CONSENT_SCRIPT_PATH, CONSENT_STYLE_PATH } from "./xidentity-consent-ui.js";
15
+ import { oauthError } from "./xidentity-problems.js";
15
16
  import { worldNow, statefulTwinManifest, twinPublicBase } from '@volter/world-core';
16
17
  import { handleXIdentityTwinRequest } from "./xidentity-twin.js";
17
18
  /**
@@ -28,8 +29,12 @@ import { handleXIdentityTwinRequest } from "./xidentity-twin.js";
28
29
  * plane by `scripts/consent-clients.ts`) — no build and no disk read on the fetch path.
29
30
  */
30
31
  export function createXIdentityTwinFetch(options = {}) {
31
- const readOnly = options.readOnly ?? false;
32
- return async function xIdentityTwinFetch(request) {
32
+ // a read-only request (x-volter-read-only, a World's read token) is a request to a read-only twin: the
33
+ // authorize screen never signs anyone in or mints a code, no token is minted or revoked, GET
34
+ // /2/users/me is served without spending the app's rate window (D3), and the kernel refuses any write
35
+ // that slips past as X's own flat OAuth error
36
+ return withRequestScopes(async function xIdentityTwinFetch(request) {
37
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
33
38
  const url = new URL(request.url);
34
39
  // GET /twin — the discovery manifest (education inside the twin).
35
40
  if (request.method === 'GET' && url.pathname.replace(/\/+$/, '') === '/twin') {
@@ -67,7 +72,12 @@ export function createXIdentityTwinFetch(options = {}) {
67
72
  }
68
73
  out['content-type'] = out['content-type'] ?? 'application/json; charset=utf-8';
69
74
  return new Response(JSON.stringify(res.body), { status: res.status, headers: out });
70
- };
75
+ }, {
76
+ refuse: () => {
77
+ const refused = oauthError('temporarily_unavailable', 'this request is read-only', 405);
78
+ return Response.json(refused.body, { status: refused.status, headers: refused.headers });
79
+ },
80
+ });
71
81
  }
72
82
  export async function createXIdentityTwinServer(options) {
73
83
  const server = await serveHttp({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-xidentity",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Local X (Twitter) identity twin — the real x.com authorize screen, the full OAuth 2.0 authorization-code + PKCE round trip (token, refresh, revoke), and GET /2/users/me with X's real field/expansion/problem envelopes, so an unmodified X client completes sign-in-with-X against it. Built on @volter/world-core.",
5
5
  "keywords": [
6
6
  "twin",
@@ -56,10 +56,10 @@
56
56
  "react-dom": "^19.2.7"
57
57
  },
58
58
  "peerDependencies": {
59
- "@volter/world-core": "2.0.1"
59
+ "@volter/world-core": "2.0.2"
60
60
  },
61
61
  "devDependencies": {
62
- "@volter/world-core": "2.0.1",
62
+ "@volter/world-core": "2.0.2",
63
63
  "@volter/world-tooling": "0.1.0",
64
64
  "@xdevplatform/xdk": "^0.6.6",
65
65
  "@types/bun": "^1.2.20",
@@ -10,8 +10,9 @@
10
10
  // • it serves the authorize page's own assets under the twin-namespaced `/_twin/assets/*`.
11
11
  //
12
12
  // It also tells the handler its OWN origin, so the consent form's action points BACK AT THE TWIN.
13
- import { serveHttp } from '@volter/world-core';
13
+ import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
14
14
  import { CONSENT_CLIENT_CSS, CONSENT_CLIENT_JS, CONSENT_SCRIPT_PATH, CONSENT_STYLE_PATH } from './xidentity-consent-ui.ts';
15
+ import { oauthError } from './xidentity-problems.ts';
15
16
  import { worldNow, statefulTwinManifest, twinPublicBase } from '@volter/world-core';
16
17
  import { handleXIdentityTwinRequest } from './xidentity-twin.ts';
17
18
 
@@ -35,8 +36,12 @@ export interface XIdentityTwinFetchOptions {
35
36
  * plane by `scripts/consent-clients.ts`) — no build and no disk read on the fetch path.
36
37
  */
37
38
  export function createXIdentityTwinFetch(options: XIdentityTwinFetchOptions = {}): (request: Request) => Promise<Response> {
38
- const readOnly = options.readOnly ?? false;
39
- return async function xIdentityTwinFetch(request: Request): Promise<Response> {
39
+ // a read-only request (x-volter-read-only, a World's read token) is a request to a read-only twin: the
40
+ // authorize screen never signs anyone in or mints a code, no token is minted or revoked, GET
41
+ // /2/users/me is served without spending the app's rate window (D3), and the kernel refuses any write
42
+ // that slips past as X's own flat OAuth error
43
+ return withRequestScopes(async function xIdentityTwinFetch(request: Request): Promise<Response> {
44
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
40
45
  const url = new URL(request.url);
41
46
  // GET /twin — the discovery manifest (education inside the twin).
42
47
  if (request.method === 'GET' && url.pathname.replace(/\/+$/, '') === '/twin') {
@@ -77,7 +82,12 @@ export function createXIdentityTwinFetch(options: XIdentityTwinFetchOptions = {}
77
82
  }
78
83
  out['content-type'] = out['content-type'] ?? 'application/json; charset=utf-8';
79
84
  return new Response(JSON.stringify(res.body), { status: res.status, headers: out });
80
- };
85
+ }, {
86
+ refuse: () => {
87
+ const refused = oauthError('temporarily_unavailable', 'this request is read-only', 405);
88
+ return Response.json(refused.body, { status: refused.status, headers: refused.headers });
89
+ },
90
+ });
81
91
  }
82
92
 
83
93
  export async function createXIdentityTwinServer(options: { root?: string; port?: number; readOnly?: boolean }): Promise<{ port: number; stop: () => void }> {