@alteran/astro 0.6.1 → 0.7.0
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 +23 -0
- package/index.js +8 -0
- package/migrations/0009_oauth_session_state.sql +31 -0
- package/migrations/meta/0009_snapshot.json +749 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +2 -1
- package/src/db/account.ts +134 -1
- package/src/db/schema.ts +31 -0
- package/src/handlers/root.ts +1 -1
- package/src/lib/appview/proxy.ts +11 -8
- package/src/lib/auth.ts +34 -3
- package/src/lib/jwt.ts +4 -0
- package/src/lib/oauth/as-keys.ts +29 -0
- package/src/lib/oauth/clients.ts +453 -24
- package/src/lib/oauth/consent.ts +180 -0
- package/src/lib/oauth/dpop.ts +39 -5
- package/src/lib/oauth/resource.ts +93 -21
- package/src/lib/oauth/store.ts +64 -7
- package/src/lib/refresh-session.ts +16 -0
- package/src/lib/session-tokens.ts +33 -5
- package/src/lib/token-cleanup.ts +4 -2
- package/src/lib/util.ts +0 -1
- package/src/pages/.well-known/oauth-authorization-server.ts +16 -3
- package/src/pages/.well-known/oauth-protected-resource.ts +8 -4
- package/src/pages/oauth/authorize.ts +31 -52
- package/src/pages/oauth/consent.ts +163 -66
- package/src/pages/oauth/jwks.ts +15 -0
- package/src/pages/oauth/par.ts +34 -56
- package/src/pages/oauth/revoke.ts +75 -0
- package/src/pages/oauth/token.ts +148 -89
- package/src/pages/xrpc/[...nsid].ts +7 -6
- package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +3 -4
- package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +3 -4
- package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +3 -4
- package/src/pages/xrpc/chat.bsky.convo.getLog.ts +3 -4
- package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +3 -4
- package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +3 -4
- package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +3 -4
- package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +3 -4
- package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +3 -4
- package/src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts +3 -4
- package/src/pages/xrpc/com.atproto.server.checkAccountStatus.ts +3 -4
- package/src/pages/xrpc/com.atproto.server.deleteSession.ts +28 -9
- package/src/pages/xrpc/com.atproto.server.getSession.ts +3 -4
- package/src/worker/runtime.ts +23 -1
- package/types/env.d.ts +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { errorMessage } from '../../lib/errors';
|
|
3
|
+
import { consumeDpopVerificationJti, verifyDpop, dpopErrorResponse } from '../../lib/oauth/dpop';
|
|
4
|
+
import { DpopNonceError } from '../../lib/oauth/dpop-errors';
|
|
5
|
+
import { publicPdsOrigin } from '../../lib/oauth/consent';
|
|
6
|
+
import { verifyAccessToken, verifyRefreshToken } from '../../lib/session-tokens';
|
|
7
|
+
import { fetchClientMetadata, requireSameClientAuth } from '../../lib/oauth/clients';
|
|
8
|
+
import { getOAuthSession, getRefreshToken, revokeOAuthSession, revokeRefreshToken } from '../../db/account';
|
|
9
|
+
|
|
10
|
+
export const prerender = false;
|
|
11
|
+
|
|
12
|
+
export async function POST({ locals, request }: APIContext) {
|
|
13
|
+
const { env } = locals.runtime;
|
|
14
|
+
try {
|
|
15
|
+
const dpop = await verifyDpop(env, request, { consumeJti: false });
|
|
16
|
+
const form = new URLSearchParams(await request.text());
|
|
17
|
+
const token = form.get('token') || '';
|
|
18
|
+
const client_id = form.get('client_id') || '';
|
|
19
|
+
if (!token || !client_id) {
|
|
20
|
+
return jsonError('invalid_request', 'Missing token or client_id', 400);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const issuer = publicPdsOrigin(env, request);
|
|
24
|
+
const clientMeta = await fetchClientMetadata(env, client_id);
|
|
25
|
+
|
|
26
|
+
const refresh = await verifyRefreshToken(env, token, { ignoreExpiration: true }).catch(() => null);
|
|
27
|
+
if (refresh?.decoded?.jti) {
|
|
28
|
+
const stored = await getRefreshToken(env, refresh.decoded.jti);
|
|
29
|
+
if (stored?.tokenKind === 'oauth' && stored.oauthSessionId) {
|
|
30
|
+
const session = await getOAuthSession(env, stored.oauthSessionId);
|
|
31
|
+
if (session && session.clientId === client_id && session.dpopJkt === dpop.jkt) {
|
|
32
|
+
await requireSameClientAuth(env, client_id, issuer, clientMeta, form, {
|
|
33
|
+
method: session.clientAuthMethod,
|
|
34
|
+
keyId: session.clientAuthKeyId,
|
|
35
|
+
});
|
|
36
|
+
await consumeDpopVerificationJti(env, dpop);
|
|
37
|
+
await revokeOAuthSession(env, session.id);
|
|
38
|
+
await revokeRefreshToken(env, stored.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return emptyOk();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const access = await verifyAccessToken(env, token).catch(() => null);
|
|
45
|
+
const sessionId = access?.oauth_session;
|
|
46
|
+
if (typeof sessionId === 'string') {
|
|
47
|
+
const session = await getOAuthSession(env, sessionId);
|
|
48
|
+
if (session && session.clientId === client_id && session.dpopJkt === dpop.jkt) {
|
|
49
|
+
await requireSameClientAuth(env, client_id, issuer, clientMeta, form, {
|
|
50
|
+
method: session.clientAuthMethod,
|
|
51
|
+
keyId: session.clientAuthKeyId,
|
|
52
|
+
});
|
|
53
|
+
await consumeDpopVerificationJti(env, dpop);
|
|
54
|
+
await revokeOAuthSession(env, session.id);
|
|
55
|
+
await revokeRefreshToken(env, session.currentRefreshTokenId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return emptyOk();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof DpopNonceError) return dpopErrorResponse(env, error);
|
|
62
|
+
return jsonError('invalid_request', errorMessage(error) ?? 'Unknown error', 400);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function emptyOk(): Response {
|
|
67
|
+
return new Response('', { status: 200 });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function jsonError(code: string, desc: string, status: number): Response {
|
|
71
|
+
return new Response(JSON.stringify({ error: code, error_description: desc }), {
|
|
72
|
+
status,
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
});
|
|
75
|
+
}
|
package/src/pages/oauth/token.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { errorMessage } from '../../lib/errors';
|
|
3
|
-
import { verifyDpop, dpopErrorResponse, getAuthzNonce } from '../../lib/oauth/dpop';
|
|
3
|
+
import { consumeDpopVerificationJti, verifyDpop, dpopErrorResponse, getAuthzNonce, sha256b64url } from '../../lib/oauth/dpop';
|
|
4
4
|
import { DpopNonceError } from '../../lib/oauth/dpop-errors';
|
|
5
|
+
import { publicPdsOrigin } from '../../lib/oauth/consent';
|
|
5
6
|
import { consumeCode } from '../../lib/oauth/store';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { issueSessionTokens, verifyRefreshToken, verifyAccessToken } from '../../lib/session-tokens';
|
|
8
|
+
import {
|
|
9
|
+
fetchClientMetadata,
|
|
10
|
+
requireSameClientAuth,
|
|
11
|
+
} from '../../lib/oauth/clients';
|
|
12
|
+
import {
|
|
13
|
+
createOAuthSession,
|
|
14
|
+
getOAuthSession,
|
|
15
|
+
getRefreshToken,
|
|
16
|
+
markOAuthRefreshUsed,
|
|
17
|
+
revokeOAuthSession,
|
|
18
|
+
storeRefreshToken,
|
|
19
|
+
updateOAuthSessionCurrent,
|
|
20
|
+
} from '../../db/account';
|
|
10
21
|
|
|
11
22
|
export const prerender = false;
|
|
12
23
|
|
|
@@ -14,19 +25,16 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
14
25
|
const { env } = locals.runtime;
|
|
15
26
|
|
|
16
27
|
try {
|
|
17
|
-
const ver = await verifyDpop(env, request);
|
|
18
|
-
|
|
28
|
+
const ver = await verifyDpop(env, request, { consumeJti: false });
|
|
19
29
|
const form = new URLSearchParams(await request.text());
|
|
20
30
|
const grant_type = form.get('grant_type') || '';
|
|
31
|
+
const issuer = publicPdsOrigin(env, request);
|
|
21
32
|
|
|
22
33
|
if (grant_type === 'authorization_code') {
|
|
23
34
|
const code = form.get('code') || '';
|
|
24
35
|
const client_id = form.get('client_id') || '';
|
|
25
36
|
const redirect_uri = form.get('redirect_uri') || '';
|
|
26
37
|
const code_verifier = form.get('code_verifier') || '';
|
|
27
|
-
const client_assertion_type = form.get('client_assertion_type') || '';
|
|
28
|
-
const client_assertion = form.get('client_assertion') || '';
|
|
29
|
-
|
|
30
38
|
if (!code || !client_id || !redirect_uri || !code_verifier) {
|
|
31
39
|
return jsonError('invalid_request', 'Missing parameters');
|
|
32
40
|
}
|
|
@@ -38,78 +46,70 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
38
46
|
|
|
39
47
|
const expected = await sha256b64url(code_verifier);
|
|
40
48
|
if (expected !== rec.code_challenge) return jsonError('invalid_grant', 'PKCE verification failed');
|
|
41
|
-
|
|
42
49
|
if (ver.jkt !== rec.dpopJkt) return jsonError('invalid_dpop', 'DPoP key mismatch');
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
const clientMeta = await fetchClientMetadata(env, client_id).catch((error) => {
|
|
52
|
+
throw new Error(`Client metadata fetch failed: ${errorMessage(error) ?? error}`);
|
|
53
|
+
});
|
|
54
|
+
await requireSameClientAuth(env, client_id, issuer, clientMeta, form, {
|
|
55
|
+
method: rec.clientAuthMethod,
|
|
56
|
+
keyId: rec.clientAuthKeyId ?? null,
|
|
57
|
+
});
|
|
58
|
+
await consumeDpopVerificationJti(env, ver);
|
|
59
|
+
|
|
60
|
+
const sessionId = crypto.randomUUID().replace(/-/g, '');
|
|
61
|
+
const accessJti = crypto.randomUUID().replace(/-/g, '');
|
|
62
|
+
const { accessJwt, refreshJwt, accessPayload, refreshPayload, refreshExpiry } = await issueSessionTokens(env, rec.did, {
|
|
63
|
+
scope: rec.scope,
|
|
64
|
+
clientId: rec.client_id,
|
|
65
|
+
dpopJkt: rec.dpopJkt,
|
|
66
|
+
oauthSessionId: sessionId,
|
|
67
|
+
accessJti,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await createOAuthSession(env, {
|
|
71
|
+
id: sessionId,
|
|
72
|
+
did: rec.did,
|
|
73
|
+
clientId: rec.client_id,
|
|
74
|
+
clientAuthMethod: rec.clientAuthMethod,
|
|
75
|
+
clientAuthKeyId: rec.clientAuthKeyId ?? null,
|
|
76
|
+
dpopJkt: rec.dpopJkt,
|
|
77
|
+
scope: rec.scope,
|
|
78
|
+
currentRefreshTokenId: refreshPayload.jti,
|
|
79
|
+
accessJti: String(accessPayload.jti),
|
|
80
|
+
expiresAt: refreshExpiry,
|
|
81
|
+
});
|
|
82
|
+
await storeRefreshToken(env, {
|
|
83
|
+
id: refreshPayload.jti,
|
|
84
|
+
did: rec.did,
|
|
85
|
+
expiresAt: refreshExpiry,
|
|
86
|
+
appPasswordName: null,
|
|
87
|
+
tokenKind: 'oauth',
|
|
88
|
+
oauthSessionId: sessionId,
|
|
89
|
+
clientId: rec.client_id,
|
|
90
|
+
clientAuthMethod: rec.clientAuthMethod,
|
|
91
|
+
clientAuthKeyId: rec.clientAuthKeyId ?? null,
|
|
92
|
+
dpopJkt: rec.dpopJkt,
|
|
93
|
+
oauthScope: rec.scope,
|
|
94
|
+
accessJti: String(accessPayload.jti),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const expires_in = accessExpiresIn(accessPayload);
|
|
98
|
+
return tokenResponse({
|
|
74
99
|
access_token: accessJwt,
|
|
75
100
|
token_type: 'DPoP',
|
|
76
101
|
expires_in,
|
|
77
102
|
refresh_token: refreshJwt,
|
|
78
103
|
scope: rec.scope,
|
|
79
104
|
sub: rec.did,
|
|
80
|
-
}
|
|
81
|
-
const headers = new Headers({ 'Content-Type': 'application/json' });
|
|
82
|
-
headers.set('DPoP-Nonce', await getAuthzNonce(env));
|
|
83
|
-
return new Response(JSON.stringify(out), { status: 200, headers });
|
|
105
|
+
}, await getAuthzNonce(env));
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
if (grant_type === 'refresh_token') {
|
|
87
109
|
const refresh_token = form.get('refresh_token') || '';
|
|
88
110
|
const client_id = form.get('client_id') || '';
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (!refresh_token) return jsonError('invalid_request', 'Missing refresh_token');
|
|
92
|
-
|
|
93
|
-
// If confidential client, verify assertion
|
|
94
|
-
if (client_id) {
|
|
95
|
-
let clientMeta: any = null;
|
|
96
|
-
try {
|
|
97
|
-
clientMeta = await fetchClientMetadata(client_id);
|
|
98
|
-
} catch {
|
|
99
|
-
// Public clients have no fetchable metadata; only confidential clients gate on it below.
|
|
100
|
-
}
|
|
101
|
-
if (clientMeta?.token_endpoint_auth_method === 'private_key_jwt') {
|
|
102
|
-
let jwks = clientMeta?.jwks;
|
|
103
|
-
if (!jwks && typeof clientMeta?.jwks_uri === 'string') {
|
|
104
|
-
const response = await fetch(clientMeta.jwks_uri);
|
|
105
|
-
jwks = await response.json();
|
|
106
|
-
}
|
|
107
|
-
const origin = `${new URL(request.url).protocol}//${new URL(request.url).host}`;
|
|
108
|
-
if (!client_assertion || client_assertion_type !== 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
|
|
109
|
-
return jsonError('invalid_client', 'Missing client assertion');
|
|
110
|
-
const ok = await verifyClientAssertion(client_id, origin, client_assertion, jwks);
|
|
111
|
-
if (!ok) return jsonError('invalid_client', 'Invalid client assertion');
|
|
112
|
-
}
|
|
111
|
+
if (!refresh_token || !client_id) {
|
|
112
|
+
return jsonError('invalid_request', 'Missing refresh_token or client_id');
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
const verification = await verifyRefreshToken(env, refresh_token).catch(() => null);
|
|
@@ -118,31 +118,79 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
118
118
|
if (verification.decoded.exp <= nowSec) return jsonError('invalid_grant', 'Expired refresh token');
|
|
119
119
|
|
|
120
120
|
const stored = await getRefreshToken(env, verification.decoded.jti);
|
|
121
|
-
if (!stored
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
await storeRefreshToken(env, { id: refreshPayload.jti, did, expiresAt: refreshExpiry, appPasswordName: stored.appPasswordName ?? null });
|
|
129
|
-
const graceExpiry = computeGraceExpiry(stored.expiresAt, nowSec);
|
|
130
|
-
await markRefreshTokenRotated(env, verification.decoded.jti, refreshPayload.jti, graceExpiry);
|
|
121
|
+
if (!stored || stored.tokenKind !== 'oauth' || !stored.oauthSessionId) {
|
|
122
|
+
return jsonError('invalid_grant', 'Refresh token revoked');
|
|
123
|
+
}
|
|
124
|
+
const session = await getOAuthSession(env, stored.oauthSessionId);
|
|
125
|
+
if (!session || session.revokedAt || session.expiresAt <= nowSec) {
|
|
126
|
+
return jsonError('invalid_grant', 'OAuth session revoked');
|
|
127
|
+
}
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
if (stored.revokedAt || stored.nextId || stored.id !== session.currentRefreshTokenId) {
|
|
130
|
+
await revokeOAuthSession(env, session.id, nowSec);
|
|
131
|
+
return jsonError('invalid_grant', 'Refresh token replayed');
|
|
132
|
+
}
|
|
133
|
+
if (stored.expiresAt <= nowSec) return jsonError('invalid_grant', 'Expired refresh token');
|
|
134
|
+
if (stored.did !== verification.decoded.sub || stored.did !== session.did) {
|
|
135
|
+
await revokeOAuthSession(env, session.id, nowSec);
|
|
136
|
+
return jsonError('invalid_grant', 'Subject mismatch');
|
|
137
|
+
}
|
|
138
|
+
if (client_id !== session.clientId) return jsonError('invalid_grant', 'client_id mismatch');
|
|
139
|
+
if (ver.jkt !== session.dpopJkt) return jsonError('invalid_dpop', 'DPoP key mismatch');
|
|
140
|
+
|
|
141
|
+
const clientMeta = await fetchClientMetadata(env, client_id).catch((error) => {
|
|
142
|
+
throw new Error(`Client metadata fetch failed: ${errorMessage(error) ?? error}`);
|
|
143
|
+
});
|
|
144
|
+
await requireSameClientAuth(env, client_id, issuer, clientMeta, form, {
|
|
145
|
+
method: session.clientAuthMethod,
|
|
146
|
+
keyId: session.clientAuthKeyId,
|
|
147
|
+
});
|
|
148
|
+
await consumeDpopVerificationJti(env, ver);
|
|
149
|
+
|
|
150
|
+
const accessJti = crypto.randomUUID().replace(/-/g, '');
|
|
151
|
+
const { accessJwt, refreshJwt, accessPayload, refreshPayload, refreshExpiry } = await issueSessionTokens(env, session.did, {
|
|
152
|
+
scope: session.scope,
|
|
153
|
+
clientId: session.clientId,
|
|
154
|
+
dpopJkt: session.dpopJkt,
|
|
155
|
+
oauthSessionId: session.id,
|
|
156
|
+
accessJti,
|
|
157
|
+
});
|
|
158
|
+
await storeRefreshToken(env, {
|
|
159
|
+
id: refreshPayload.jti,
|
|
160
|
+
did: session.did,
|
|
161
|
+
expiresAt: refreshExpiry,
|
|
162
|
+
appPasswordName: null,
|
|
163
|
+
tokenKind: 'oauth',
|
|
164
|
+
oauthSessionId: session.id,
|
|
165
|
+
clientId: session.clientId,
|
|
166
|
+
clientAuthMethod: session.clientAuthMethod as any,
|
|
167
|
+
clientAuthKeyId: session.clientAuthKeyId,
|
|
168
|
+
dpopJkt: session.dpopJkt,
|
|
169
|
+
oauthScope: session.scope,
|
|
170
|
+
accessJti: String(accessPayload.jti),
|
|
171
|
+
});
|
|
172
|
+
try {
|
|
173
|
+
await markOAuthRefreshUsed(env, stored.id, refreshPayload.jti, nowSec);
|
|
174
|
+
await updateOAuthSessionCurrent(env, session.id, {
|
|
175
|
+
currentRefreshTokenId: refreshPayload.jti,
|
|
176
|
+
previousRefreshTokenId: stored.id,
|
|
177
|
+
accessJti: String(accessPayload.jti),
|
|
178
|
+
expiresAt: refreshExpiry,
|
|
179
|
+
now: nowSec,
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
182
|
+
await revokeOAuthSession(env, session.id, nowSec);
|
|
183
|
+
return jsonError('invalid_grant', 'Refresh token replayed');
|
|
184
|
+
}
|
|
134
185
|
|
|
135
|
-
|
|
186
|
+
return tokenResponse({
|
|
136
187
|
access_token: accessJwt,
|
|
137
188
|
token_type: 'DPoP',
|
|
138
|
-
expires_in,
|
|
189
|
+
expires_in: accessExpiresIn(accessPayload),
|
|
139
190
|
refresh_token: refreshJwt,
|
|
140
|
-
scope:
|
|
141
|
-
sub: did,
|
|
142
|
-
}
|
|
143
|
-
const headers = new Headers({ 'Content-Type': 'application/json' });
|
|
144
|
-
headers.set('DPoP-Nonce', await getAuthzNonce(env));
|
|
145
|
-
return new Response(JSON.stringify(out), { status: 200, headers });
|
|
191
|
+
scope: session.scope,
|
|
192
|
+
sub: session.did,
|
|
193
|
+
}, await getAuthzNonce(env));
|
|
146
194
|
}
|
|
147
195
|
|
|
148
196
|
return jsonError('unsupported_grant_type', 'grant_type must be authorization_code or refresh_token');
|
|
@@ -152,6 +200,17 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
152
200
|
}
|
|
153
201
|
}
|
|
154
202
|
|
|
203
|
+
function accessExpiresIn(payload: Awaited<ReturnType<typeof verifyAccessToken>> | Record<string, unknown>): number {
|
|
204
|
+
const now = Math.floor(Date.now() / 1000);
|
|
205
|
+
return typeof payload.exp === 'number' ? Math.max(0, payload.exp - now) : 7200;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function tokenResponse(body: Record<string, unknown>, nonce: string): Response {
|
|
209
|
+
const headers = new Headers({ 'Content-Type': 'application/json' });
|
|
210
|
+
headers.set('DPoP-Nonce', nonce);
|
|
211
|
+
return new Response(JSON.stringify(body), { status: 200, headers });
|
|
212
|
+
}
|
|
213
|
+
|
|
155
214
|
function jsonError(code: string, desc?: string): Response {
|
|
156
215
|
const headers = new Headers({ 'Content-Type': 'application/json' });
|
|
157
216
|
return new Response(JSON.stringify({ error: code, error_description: desc }), { status: 400, headers });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { proxyAppView } from '../../lib/appview';
|
|
3
|
-
import {
|
|
3
|
+
import { authErrorResponse, authenticateRequest, unauthorized } from '../../lib/auth';
|
|
4
4
|
|
|
5
5
|
export const prerender = false;
|
|
6
6
|
|
|
@@ -20,12 +20,13 @@ function nsidFromParams(params: Record<string, any>): string {
|
|
|
20
20
|
|
|
21
21
|
async function handle({ locals, request, params }: APIContext): Promise<Response> {
|
|
22
22
|
const { env } = locals.runtime;
|
|
23
|
+
let auth;
|
|
23
24
|
try {
|
|
24
|
-
|
|
25
|
+
auth = await authenticateRequest(request, env);
|
|
26
|
+
if (!auth) return unauthorized();
|
|
25
27
|
} catch (error) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
28
|
+
const handled = await authErrorResponse(env, error);
|
|
29
|
+
if (handled) return handled;
|
|
29
30
|
throw error;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -45,7 +46,7 @@ async function handle({ locals, request, params }: APIContext): Promise<Response
|
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
return proxyAppView({ request, env, lxm: nsid });
|
|
49
|
+
return proxyAppView({ request, env, lxm: nsid, auth });
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export async function GET(ctx: APIContext) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
import { getActorPreferences } from '../../lib/preferences';
|
|
4
4
|
|
|
5
5
|
export const prerender = false;
|
|
@@ -9,9 +9,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
9
9
|
try {
|
|
10
10
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
11
11
|
} catch (error) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
12
|
+
const handled = await authErrorResponse(env, error);
|
|
13
|
+
if (handled) return handled;
|
|
15
14
|
throw error;
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { errorCode, errorMessage } from '../../lib/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
4
|
import { readJsonBounded } from '../../lib/util';
|
|
5
5
|
import { setActorPreferences } from '../../lib/preferences';
|
|
6
6
|
|
|
@@ -11,9 +11,8 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
11
11
|
try {
|
|
12
12
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
13
13
|
} catch (error) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
14
|
+
const handled = await authErrorResponse(env, error);
|
|
15
|
+
if (handled) return handled;
|
|
17
16
|
throw error;
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
|
|
4
4
|
export const prerender = false;
|
|
5
5
|
|
|
@@ -8,9 +8,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
8
8
|
try {
|
|
9
9
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
10
10
|
} catch (error) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
11
|
+
const handled = await authErrorResponse(env, error);
|
|
12
|
+
if (handled) return handled;
|
|
14
13
|
throw error;
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
import { getPrimaryActor } from '../../lib/actor';
|
|
4
4
|
import { listChatConvoLogs } from '../../lib/chat';
|
|
5
5
|
|
|
@@ -10,9 +10,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
10
10
|
try {
|
|
11
11
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
12
12
|
} catch (error) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
const handled = await authErrorResponse(env, error);
|
|
14
|
+
if (handled) return handled;
|
|
16
15
|
throw error;
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
import { listChatConvos } from '../../lib/chat';
|
|
4
4
|
import { getPrimaryActor } from '../../lib/actor';
|
|
5
5
|
|
|
@@ -10,9 +10,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
10
10
|
try {
|
|
11
11
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
12
12
|
} catch (error) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
const handled = await authErrorResponse(env, error);
|
|
14
|
+
if (handled) return handled;
|
|
16
15
|
throw error;
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
import { resolveSecret } from '../../lib/secrets';
|
|
4
4
|
|
|
5
5
|
export const prerender = false;
|
|
@@ -17,9 +17,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
17
17
|
try {
|
|
18
18
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
19
19
|
} catch (error) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
20
|
+
const handled = await authErrorResponse(env, error);
|
|
21
|
+
if (handled) return handled;
|
|
23
22
|
throw error;
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
|
|
4
4
|
export const prerender = false;
|
|
5
5
|
|
|
@@ -20,9 +20,8 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
20
20
|
return unauthorized();
|
|
21
21
|
}
|
|
22
22
|
} catch (error) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
23
|
+
const handled = await authErrorResponse(env, error);
|
|
24
|
+
if (handled) return handled;
|
|
26
25
|
throw error;
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
|
-
import {
|
|
2
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
3
3
|
import { resolveSecret } from '../../lib/secrets';
|
|
4
4
|
|
|
5
5
|
export const prerender = false;
|
|
@@ -18,9 +18,8 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
18
18
|
try {
|
|
19
19
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
20
20
|
} catch (error) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
21
|
+
const handled = await authErrorResponse(env, error);
|
|
22
|
+
if (handled) return handled;
|
|
24
23
|
throw error;
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { errorMessage } from '../../lib/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
4
|
import { resolveSecret } from '../../lib/secrets';
|
|
5
5
|
|
|
6
6
|
export const prerender = false;
|
|
@@ -18,9 +18,8 @@ export async function POST({ locals, request }: APIContext) {
|
|
|
18
18
|
try {
|
|
19
19
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
20
20
|
} catch (error) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
21
|
+
const handled = await authErrorResponse(env, error);
|
|
22
|
+
if (handled) return handled;
|
|
24
23
|
throw error;
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { errorMessage } from '../../lib/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
4
|
import { getDb } from '../../db/client';
|
|
5
5
|
import { record, blob_ref } from '../../db/schema';
|
|
6
6
|
import { eq } from 'drizzle-orm';
|
|
@@ -20,9 +20,8 @@ export async function GET({ locals, request, url }: APIContext) {
|
|
|
20
20
|
try {
|
|
21
21
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
22
22
|
} catch (error) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
23
|
+
const handled = await authErrorResponse(env, error);
|
|
24
|
+
if (handled) return handled;
|
|
26
25
|
throw error;
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { APIContext } from 'astro';
|
|
2
2
|
import { errorMessage } from '../../lib/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { authErrorResponse, isAuthorized, unauthorized } from '../../lib/auth';
|
|
4
4
|
import { getAccountState } from '../../db/dal';
|
|
5
5
|
import { toWireStatus } from '../../lib/account-state';
|
|
6
6
|
import { getDb } from '../../db/client';
|
|
@@ -25,9 +25,8 @@ export async function GET({ locals, request }: APIContext) {
|
|
|
25
25
|
try {
|
|
26
26
|
if (!(await isAuthorized(request, env))) return unauthorized();
|
|
27
27
|
} catch (error) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
28
|
+
const handled = await authErrorResponse(env, error);
|
|
29
|
+
if (handled) return handled;
|
|
31
30
|
throw error;
|
|
32
31
|
}
|
|
33
32
|
|