@volter/twin-xidentity 0.1.0 → 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.
- package/README.md +37 -4
- package/client/xidentity-consent.tsx +41 -4
- package/dist/client/xidentity-consent.bundle.js +85 -85
- package/dist/client/xidentity-consent.d.ts +17 -1
- package/dist/client/xidentity-consent.js +11 -2
- package/dist/client/xidentity-consent.tsx +41 -4
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +16 -8
- package/dist/src/xidentity-capabilities.js +243 -8
- package/dist/src/xidentity-conformance.d.ts +41 -0
- package/dist/src/xidentity-conformance.js +210 -3
- package/dist/src/xidentity-consent-client.gen.d.ts +1 -1
- package/dist/src/xidentity-consent-client.gen.js +1 -1
- package/dist/src/xidentity-consent-ui.d.ts +25 -1
- package/dist/src/xidentity-consent-ui.js +62 -1
- package/dist/src/xidentity-oauth1.d.ts +93 -0
- package/dist/src/xidentity-oauth1.js +594 -0
- package/dist/src/xidentity-server.js +14 -4
- package/dist/src/xidentity-store.d.ts +4 -1
- package/dist/src/xidentity-store.js +8 -1
- package/dist/src/xidentity-twin.js +32 -9
- package/package.json +5 -4
- package/src/index.ts +30 -8
- package/src/xidentity-capabilities.ts +254 -8
- package/src/xidentity-conformance.ts +232 -3
- package/src/xidentity-consent-client.gen.ts +1 -1
- package/src/xidentity-consent-ui.ts +67 -0
- package/src/xidentity-oauth1.ts +598 -0
- package/src/xidentity-server.ts +14 -4
- package/src/xidentity-store.ts +8 -1
- package/src/xidentity-twin.ts +30 -9
package/README.md
CHANGED
|
@@ -41,10 +41,10 @@ with X's consent descriptions; the **docs.x.com OAuth 2.0 guides** for the autho
|
|
|
41
41
|
revoke requests; the **docs.x.com rate-limit pages** for the `x-rate-limit-*` headers and the
|
|
42
42
|
75/15-minute per-user figure; and the **official SDK sources** (`@xdevplatform/xdk`,
|
|
43
43
|
`twitter-api-typescript-sdk`) for the token/revoke response shapes the docs never show as JSON.
|
|
44
|
-
It currently reads **
|
|
44
|
+
It currently reads **98 done / 160 covered** (62 `todo`, and the login leg is not an API surface: it is the seeded session, so it is not in that
|
|
45
45
|
denominator). The `todo`s are real X identity
|
|
46
|
-
surface this twin does not model — most notably **
|
|
47
|
-
|
|
46
|
+
surface this twin does not model — most notably the **v1.1 app-only bearer flow**, OAuth 1.0a's
|
|
47
|
+
timestamp window and nonce replay, the unmodelled `user.fields` (entities, withheld, subscription, the
|
|
48
48
|
relational fields), expansion hydration — plus a family of **wire-pinning todos**: behaviours the
|
|
49
49
|
twin models from RFC 6749/7009 or widely-reported captures because no fetched official artefact
|
|
50
50
|
states them (exact token error wording, the authorize error channel, refresh-token rotation, the
|
|
@@ -74,6 +74,37 @@ code TTL). Each such `done` names its evidence boundary in the manifest and its
|
|
|
74
74
|
(`xidentity-sdk.integration.test.ts`); the OAuth legs its hardcoded hosts cannot re-aim are
|
|
75
75
|
proven over real transport in the docs' own curl shapes.
|
|
76
76
|
|
|
77
|
+
### OAuth 1.0a (`src/xidentity-oauth1.ts`)
|
|
78
|
+
|
|
79
|
+
The three-legged flow as docs.x.com's API reference and "Obtaining access tokens using 3-legged
|
|
80
|
+
OAuth flow" state it, which is what Postiz (`gitroomhq/postiz-app`) connects an X account with
|
|
81
|
+
through `twitter-api-v2`:
|
|
82
|
+
|
|
83
|
+
- `POST oauth/request_token`, signed by a registered App with HMAC-SHA1, answers `oauth_token`,
|
|
84
|
+
`oauth_token_secret` and `oauth_callback_confirmed=true`; a callback the App does not list is
|
|
85
|
+
code 415, `oob` is the PIN flow, and `x_auth_access_type=read` narrows a Read-and-write App.
|
|
86
|
+
- `GET oauth/authorize` and `GET oauth/authenticate` (served on `api.x.com`, where X serves them)
|
|
87
|
+
show the screen naming the App and the signed-in account, listing what its permission allows;
|
|
88
|
+
its form posts back to `POST oauth/authorize` on the same host, as X's screen does (on api.x.com,
|
|
89
|
+
which the `x` pack shares, that path and the screen's `/_twin/assets/consent.{js,css}` are this
|
|
90
|
+
pack's). Authorize app returns to the callback with exactly `oauth_token` + `oauth_verifier`, Cancel with
|
|
91
|
+
`denied=<request token>`. `authenticate` skips the screen once the person approved the App
|
|
92
|
+
(unless `force_login=true`).
|
|
93
|
+
- `POST oauth/access_token` trades the approved request token and verifier, once, for
|
|
94
|
+
`<user id>-<token>`, its secret, `user_id` and `screen_name`; approving the same App again returns
|
|
95
|
+
the token already held. `POST 1.1/oauth/invalidate_token` revokes it.
|
|
96
|
+
- The access token signs `GET /2/users/me` here and the posting surface in the `x` pack (which
|
|
97
|
+
reads this pack's `oauth1_token` rows). An unknown consumer key or a signature that does not
|
|
98
|
+
verify is code 32 at the legs and the `about:blank` 401 on `/2`.
|
|
99
|
+
- The **World's own App** is the pair the World's env names (`X_API_KEY` / `X_API_SECRET`, or
|
|
100
|
+
`TWITTER_API_KEY` / `TWITTER_API_SECRET`), read with `worldEnvValue`, named for the World and
|
|
101
|
+
accepting any callback — the slack pack's World-app rule. Other Apps are registered through
|
|
102
|
+
`POST /_twin/oauth1_apps`.
|
|
103
|
+
|
|
104
|
+
Not modelled (manifest todos): the timestamp window and nonce replay (the twin's clock is the
|
|
105
|
+
World's, an app signs with its host's), request-token expiry, the login step `force_login` and
|
|
106
|
+
`screen_name` would show, and the live wording of several refusals, each pinned by name.
|
|
107
|
+
|
|
77
108
|
### What is not
|
|
78
109
|
|
|
79
110
|
The twin **authenticates nobody**: no password, no 2FA, no risk engine. The
|
|
@@ -86,7 +117,9 @@ service-areas to add within this same pack; they are not a reason to create a se
|
|
|
86
117
|
|
|
87
118
|
`GET /_twin/consent` (re-render a pending authorize screen by request handle) and
|
|
88
119
|
`POST /_twin/consent` (the Authorize/Cancel form post — X's real form posts to an undocumented
|
|
89
|
-
internal endpoint
|
|
120
|
+
internal endpoint; the OAuth 1.0a screen's posts to `POST oauth/authorize`, above),
|
|
121
|
+
`POST /_twin/oauth1_apps` (register an OAuth 1.0a App: consumer key and secret, callback URLs,
|
|
122
|
+
permission), `POST /_twin/clients`, `POST /_twin/accounts`, `POST /_twin/session`,
|
|
90
123
|
`POST /_twin/rate_limit` (arm a deterministic 429), and the `/_twin/assets/*` page assets. This
|
|
91
124
|
list is the audit trail the conformance census deliberately excludes — keep it in lockstep with
|
|
92
125
|
`twinControl`'s branches in `xidentity-twin.ts`.
|
|
@@ -44,8 +44,12 @@ export type ConsentView = {
|
|
|
44
44
|
/** The x.com session this browser is signed in as — the account that will consent. */
|
|
45
45
|
account: ConsentAccount;
|
|
46
46
|
scopes: ConsentScopeRow[];
|
|
47
|
-
/** Host of the validated redirect_uri, shown in the "you'll be redirected to" notice.
|
|
47
|
+
/** Host of the validated redirect_uri, shown in the "you'll be redirected to" notice. Empty for
|
|
48
|
+
* an OAuth 1.0a out-of-band (PIN) request, which redirects nowhere. */
|
|
48
49
|
redirectHost: string;
|
|
50
|
+
/** Where the decision posts, under `origin`: `/_twin/consent` for an OAuth 2.0 authorize screen,
|
|
51
|
+
* `/oauth/authorize` for an OAuth 1.0a one, as X's own screen posts (the request token is the handle). */
|
|
52
|
+
decisionPath?: string;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
/** The X mark, drawn rather than fetched — a twin never reaches out to a vendor CDN. */
|
|
@@ -96,7 +100,7 @@ export function ConsentPage({ view }: { view: ConsentView }) {
|
|
|
96
100
|
<ScopeSection heading={`Things ${appName} can view`} rows={view.scopes.filter((s) => s.group === 'view')} />
|
|
97
101
|
<ScopeSection heading={`Things ${appName} can do`} rows={view.scopes.filter((s) => s.group === 'do')} />
|
|
98
102
|
<ScopeSection heading="Until you revoke access" rows={view.scopes.filter((s) => s.group === 'session')} />
|
|
99
|
-
<form method="POST" action={`${view.origin}/_twin/consent`} className="consent-form">
|
|
103
|
+
<form method="POST" action={`${view.origin}${view.decisionPath ?? '/_twin/consent'}`} className="consent-form">
|
|
100
104
|
<input type="hidden" name="auth_request" value={view.requestId} />
|
|
101
105
|
<div className="actions">
|
|
102
106
|
<button className="btn btn-primary" type="submit" name="decision" value="allow">
|
|
@@ -108,8 +112,41 @@ export function ConsentPage({ view }: { view: ConsentView }) {
|
|
|
108
112
|
</div>
|
|
109
113
|
</form>
|
|
110
114
|
<p className="legal">
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
{view.redirectHost ? (
|
|
116
|
+
<>
|
|
117
|
+
You'll be redirected to <strong className="redirect-host">{view.redirectHost}</strong>.{' '}
|
|
118
|
+
</>
|
|
119
|
+
) : null}
|
|
120
|
+
You can revoke access to any app at any time from the Apps and sessions section of your X settings.
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type PinPageProps = {
|
|
127
|
+
appName: string;
|
|
128
|
+
/** The oauth_verifier, shown as the PIN the person types into the app (out-of-band flow). */
|
|
129
|
+
pin: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The OAuth 1.0a out-of-band ending: an app that registered `oauth_callback=oob` gets no redirect,
|
|
134
|
+
* so X shows the verifier as a PIN for the person to type into the app (docs.x.com PIN-based
|
|
135
|
+
* authorization). The layout follows the classic screen as widely screenshotted; its live DOM was
|
|
136
|
+
* not captured (`xidentity.oauth1.consent_wording`, todo).
|
|
137
|
+
*/
|
|
138
|
+
export function PinPage({ appName, pin }: PinPageProps) {
|
|
139
|
+
return (
|
|
140
|
+
<div className="card">
|
|
141
|
+
<header className="x-header">
|
|
142
|
+
<XMark />
|
|
143
|
+
</header>
|
|
144
|
+
<h1 className="title">
|
|
145
|
+
You've granted access to <strong className="app-name">{appName}</strong>!
|
|
146
|
+
</h1>
|
|
147
|
+
<p className="legal">Next, return to {appName} and enter this PIN to complete the authorization process:</p>
|
|
148
|
+
<p className="title">
|
|
149
|
+
<code className="oauth-pin">{pin}</code>
|
|
113
150
|
</p>
|
|
114
151
|
</div>
|
|
115
152
|
);
|