@hatk/hatk 0.0.1-alpha.46 → 0.0.1-alpha.47
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/dist/oauth/server.d.ts +11 -0
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +54 -14
- package/package.json +1 -1
package/dist/oauth/server.d.ts
CHANGED
|
@@ -59,6 +59,17 @@ export declare function getClientMetadata(issuer: string, config: OAuthConfig):
|
|
|
59
59
|
dpop_bound_access_tokens: boolean;
|
|
60
60
|
scope: string;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Handle a Pushed Authorization Request (PAR).
|
|
64
|
+
*
|
|
65
|
+
* Supports account creation via `prompt=create`. When set, `login_hint`
|
|
66
|
+
* is treated as a PDS hostname (e.g. "selfhosted.social" or "localhost:2583")
|
|
67
|
+
* rather than a handle or DID. The auth server is discovered from the PDS's
|
|
68
|
+
* protected resource metadata, and `prompt=create` is forwarded to the PDS
|
|
69
|
+
* PAR so it shows the signup page.
|
|
70
|
+
*
|
|
71
|
+
* For normal login, `login_hint` is a handle or DID as usual.
|
|
72
|
+
*/
|
|
62
73
|
export declare function handlePar(config: OAuthConfig, body: Record<string, string>, dpopHeader: string, requestUrl: string): Promise<{
|
|
63
74
|
request_uri: string;
|
|
64
75
|
expires_in: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AA4E/C,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBrG;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;EAqBxE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;EAO/E;AAED,wBAAgB,OAAO;;;;;;;;;;;;;;;;;;;;;;EAWtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;EAcpE;AAID,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AA4E/C,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBrG;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;EAqBxE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;EAO/E;AAED,wBAAgB,OAAO;;;;;;;;;;;;;;;;;;;;;;EAWtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;EAcpE;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAwKtD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAShF;AAID,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsGtF;AAID,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA0HrG;AAID,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAUd;AA0JD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtF,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoEpF;AAID,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA0BjC"}
|
package/dist/oauth/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { generateKeyPair, importPrivateKey, computeJwkThumbprint, signJwt, parse
|
|
|
3
3
|
import { parseDpopProof, createDpopProof } from "./dpop.js";
|
|
4
4
|
import { initSession } from "./session.js";
|
|
5
5
|
import { resolveClient, validateRedirectUri, isLoopbackClient } from "./client.js";
|
|
6
|
-
import { discoverAuthServer, resolveHandle } from "./discovery.js";
|
|
6
|
+
import { discoverAuthServer, resolveHandle, fetchProtectedResourceMetadata, fetchAuthServerMetadata } from "./discovery.js";
|
|
7
7
|
import { getServerKey, storeServerKey, storeOAuthRequest, getOAuthRequest, deleteOAuthRequest, storeAuthCode, consumeAuthCode, storeSession, deleteSession, checkAndStoreDpopJti, cleanupExpiredOAuth, storeRefreshToken, getRefreshToken, revokeRefreshToken, } from "./db.js";
|
|
8
8
|
import { emit } from "../logger.js";
|
|
9
9
|
import { querySQL } from "../database/db.js";
|
|
@@ -122,6 +122,17 @@ export function getClientMetadata(issuer, config) {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
// --- PAR Endpoint ---
|
|
125
|
+
/**
|
|
126
|
+
* Handle a Pushed Authorization Request (PAR).
|
|
127
|
+
*
|
|
128
|
+
* Supports account creation via `prompt=create`. When set, `login_hint`
|
|
129
|
+
* is treated as a PDS hostname (e.g. "selfhosted.social" or "localhost:2583")
|
|
130
|
+
* rather than a handle or DID. The auth server is discovered from the PDS's
|
|
131
|
+
* protected resource metadata, and `prompt=create` is forwarded to the PDS
|
|
132
|
+
* PAR so it shows the signup page.
|
|
133
|
+
*
|
|
134
|
+
* For normal login, `login_hint` is a handle or DID as usual.
|
|
135
|
+
*/
|
|
125
136
|
export async function handlePar(config, body, dpopHeader, requestUrl) {
|
|
126
137
|
// Validate client DPoP proof
|
|
127
138
|
const dpop = await parseDpopProof(dpopHeader, 'POST', requestUrl);
|
|
@@ -146,9 +157,34 @@ export async function handlePar(config, body, dpopHeader, requestUrl) {
|
|
|
146
157
|
throw new Error('code_challenge is required');
|
|
147
158
|
if (body.code_challenge_method && body.code_challenge_method !== 'S256')
|
|
148
159
|
throw new Error('Only S256 supported');
|
|
149
|
-
// Resolve DID from login_hint
|
|
160
|
+
// Resolve DID and PDS from login_hint
|
|
161
|
+
const prompt = body.prompt;
|
|
150
162
|
let did = body.login_hint;
|
|
151
|
-
|
|
163
|
+
let pdsRequestUri;
|
|
164
|
+
let pdsAuthServer;
|
|
165
|
+
let pdsCodeVerifier;
|
|
166
|
+
let pdsState;
|
|
167
|
+
let pdsEndpoint;
|
|
168
|
+
if (prompt === 'create' && body.login_hint) {
|
|
169
|
+
// Account creation: login_hint is a PDS URL, discover auth server from it directly
|
|
170
|
+
let pdsUrl;
|
|
171
|
+
if (body.login_hint.startsWith('http')) {
|
|
172
|
+
pdsUrl = body.login_hint;
|
|
173
|
+
}
|
|
174
|
+
else if (body.login_hint.match(/^localhost[:/]/)) {
|
|
175
|
+
pdsUrl = `http://${body.login_hint}`;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
pdsUrl = `https://${body.login_hint}`;
|
|
179
|
+
}
|
|
180
|
+
pdsEndpoint = pdsUrl;
|
|
181
|
+
const protectedResource = await fetchProtectedResourceMetadata(pdsUrl);
|
|
182
|
+
pdsAuthServer = protectedResource.authorization_servers[0];
|
|
183
|
+
if (!pdsAuthServer)
|
|
184
|
+
throw new Error(`No auth server for PDS ${pdsUrl}`);
|
|
185
|
+
did = undefined; // no DID yet for account creation
|
|
186
|
+
}
|
|
187
|
+
else if (did && !did.startsWith('did:')) {
|
|
152
188
|
try {
|
|
153
189
|
did = await resolveHandle(did, _relayUrl);
|
|
154
190
|
}
|
|
@@ -156,33 +192,37 @@ export async function handlePar(config, body, dpopHeader, requestUrl) {
|
|
|
156
192
|
throw new Error('Handle not found');
|
|
157
193
|
}
|
|
158
194
|
}
|
|
159
|
-
// Discover user's PDS auth server
|
|
160
|
-
|
|
161
|
-
let pdsAuthServer;
|
|
162
|
-
let pdsCodeVerifier;
|
|
163
|
-
let pdsState;
|
|
164
|
-
let pdsEndpoint;
|
|
165
|
-
if (did) {
|
|
195
|
+
// Discover user's PDS auth server (for login flow with a resolved DID)
|
|
196
|
+
if (did && !pdsAuthServer) {
|
|
166
197
|
const discovery = await discoverAuthServer(did, _plcUrl);
|
|
167
198
|
pdsAuthServer = discovery.authServerEndpoint;
|
|
168
199
|
pdsEndpoint = discovery.pdsEndpoint;
|
|
200
|
+
}
|
|
201
|
+
if (pdsAuthServer) {
|
|
202
|
+
const authServerMetadata = await fetchAuthServerMetadata(pdsAuthServer);
|
|
169
203
|
// Create PKCE for our PAR to the PDS
|
|
170
204
|
pdsCodeVerifier = randomToken();
|
|
171
205
|
const pdsCodeChallenge = base64UrlEncode(await sha256(pdsCodeVerifier));
|
|
172
206
|
pdsState = randomToken(); // unique state to correlate callback
|
|
173
207
|
// PAR to the PDS
|
|
174
|
-
const parEndpoint =
|
|
208
|
+
const parEndpoint = authServerMetadata.pushed_authorization_request_endpoint || `${pdsAuthServer}/oauth/par`;
|
|
175
209
|
const serverDpopProof = await createDpopProof(serverPrivateJwk, serverPublicJwk, 'POST', parEndpoint);
|
|
176
|
-
const
|
|
210
|
+
const pdsParParams = {
|
|
177
211
|
client_id: pdsClientId(config.issuer, config),
|
|
178
212
|
redirect_uri: pdsRedirectUri(config.issuer),
|
|
179
213
|
response_type: 'code',
|
|
180
214
|
code_challenge: pdsCodeChallenge,
|
|
181
215
|
code_challenge_method: 'S256',
|
|
182
216
|
scope: body.scope || 'atproto transition:generic',
|
|
183
|
-
login_hint: body.login_hint || did,
|
|
184
217
|
state: pdsState,
|
|
185
|
-
}
|
|
218
|
+
};
|
|
219
|
+
if (prompt === 'create') {
|
|
220
|
+
pdsParParams.prompt = 'create';
|
|
221
|
+
}
|
|
222
|
+
if (did) {
|
|
223
|
+
pdsParParams.login_hint = body.login_hint || did;
|
|
224
|
+
}
|
|
225
|
+
const pdsParBody = new URLSearchParams(pdsParParams);
|
|
186
226
|
const pdsParRes = await fetch(parEndpoint, {
|
|
187
227
|
method: 'POST',
|
|
188
228
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: serverDpopProof },
|