@q32/core 0.1.21 → 0.1.23
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 +8 -0
- package/dist/auth.d.ts +99 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +232 -0
- package/dist/auth.js.map +1 -0
- package/dist/hono.d.ts +22 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/hono.js +26 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/react-router.d.ts +14 -0
- package/dist/react-router.d.ts.map +1 -0
- package/dist/react-router.js +30 -0
- package/dist/react-router.js.map +1 -0
- package/docs/evaluation.md +5 -4
- package/package.json +13 -1
- package/src/auth.ts +341 -0
- package/src/hono.ts +54 -0
- package/src/index.ts +3 -0
- package/src/react-router.ts +53 -0
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ The first release focuses on common infrastructure repeated across Q32 apps:
|
|
|
6
6
|
|
|
7
7
|
- AI provider contracts and JSON extraction helpers
|
|
8
8
|
- API operation registries
|
|
9
|
+
- framework-neutral auth/session/MCP auth services with thin Hono and React Router adapters
|
|
9
10
|
- billing plan/status primitives
|
|
10
11
|
- Cloudflare binding guards
|
|
11
12
|
- environment parsing
|
|
@@ -50,9 +51,12 @@ import {
|
|
|
50
51
|
D1JobStore,
|
|
51
52
|
D1_JOBS_SCHEMA,
|
|
52
53
|
appUrl,
|
|
54
|
+
createAuthSystem,
|
|
53
55
|
createId,
|
|
56
|
+
honoAuthMiddleware,
|
|
54
57
|
jsonResponse,
|
|
55
58
|
oauthAuthorizationServerMetadata,
|
|
59
|
+
reactRouterAuthContext,
|
|
56
60
|
renderSitemapXml,
|
|
57
61
|
recordOpsEvent,
|
|
58
62
|
signSession,
|
|
@@ -60,3 +64,7 @@ import {
|
|
|
60
64
|
```
|
|
61
65
|
|
|
62
66
|
See [docs/evaluation.md](docs/evaluation.md) for the project-inventory replacement matrix and [docs/jobs.md](docs/jobs.md) for the shared jobs model.
|
|
67
|
+
|
|
68
|
+
Framework adapters are deliberately thin. Put policy and persistence in `auth`,
|
|
69
|
+
`jobs`, `ops-events`, and `api`; use `hono` or `react-router` only to translate a
|
|
70
|
+
framework request/response shape into those shared services.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type McpOAuthRepository, type McpTokenRow, type OAuthClientInformation, type OAuthClientRegistration, type OAuthTokens } from "./oauth.js";
|
|
2
|
+
export type CookieOptions = {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export type SessionAuthOptions<TSession> = {
|
|
6
|
+
secret: string;
|
|
7
|
+
cookie: CookieOptions;
|
|
8
|
+
verifyToken?: (token: string | undefined, secret: string, request: Request) => Promise<TSession | null> | TSession | null;
|
|
9
|
+
verify?: (payload: TSession) => Promise<TSession | null> | TSession | null;
|
|
10
|
+
};
|
|
11
|
+
export type AuthContext<TPrincipal = unknown, TSession = unknown> = {
|
|
12
|
+
request: Request;
|
|
13
|
+
session: TSession | null;
|
|
14
|
+
principal: TPrincipal | null;
|
|
15
|
+
};
|
|
16
|
+
export type AuthSystemOptions<TPrincipal, TSession> = {
|
|
17
|
+
session?: SessionAuthOptions<TSession>;
|
|
18
|
+
loadPrincipal?: (session: TSession, request: Request) => Promise<TPrincipal | null> | TPrincipal | null;
|
|
19
|
+
};
|
|
20
|
+
export declare class AuthSystem<TPrincipal = unknown, TSession = unknown> {
|
|
21
|
+
private readonly options;
|
|
22
|
+
constructor(options: AuthSystemOptions<TPrincipal, TSession>);
|
|
23
|
+
sessionFromRequest(request: Request): Promise<TSession | null>;
|
|
24
|
+
contextFromRequest(request: Request): Promise<AuthContext<TPrincipal, TSession>>;
|
|
25
|
+
requirePrincipal(request: Request): Promise<TPrincipal>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createAuthSystem<TPrincipal = unknown, TSession = unknown>(options: AuthSystemOptions<TPrincipal, TSession>): AuthSystem<TPrincipal, TSession>;
|
|
28
|
+
export declare function getCookie(request: Request, name: string): string | undefined;
|
|
29
|
+
export declare function requireAdminRequest(request: Request, expectedToken: string | undefined): void;
|
|
30
|
+
export type McpAuthInfo<TSubject extends Record<string, string> = Record<string, string>> = {
|
|
31
|
+
token: string;
|
|
32
|
+
clientId: string;
|
|
33
|
+
scopes: string[];
|
|
34
|
+
expiresAt: number;
|
|
35
|
+
resource?: URL;
|
|
36
|
+
subject: TSubject;
|
|
37
|
+
extra?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
export type McpAuthorizationParams = {
|
|
40
|
+
redirectUri: string;
|
|
41
|
+
state?: string;
|
|
42
|
+
scopes?: string[];
|
|
43
|
+
resource?: URL;
|
|
44
|
+
codeChallenge: string;
|
|
45
|
+
};
|
|
46
|
+
export type McpAuthorizeResult = {
|
|
47
|
+
kind: "redirect";
|
|
48
|
+
location: string;
|
|
49
|
+
status?: number;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "authorized";
|
|
52
|
+
code: string;
|
|
53
|
+
redirect: string;
|
|
54
|
+
};
|
|
55
|
+
export type McpAuthServiceOptions<TPrincipal, TSubject extends Record<string, string>> = {
|
|
56
|
+
repository: McpOAuthRepository;
|
|
57
|
+
sessionAuth?: AuthSystem<TPrincipal, unknown>;
|
|
58
|
+
loginPath?: string;
|
|
59
|
+
defaultScopes?: string[];
|
|
60
|
+
authCodeTtlSeconds?: number;
|
|
61
|
+
accessTokenTtlSeconds?: number;
|
|
62
|
+
refreshTokenTtlSeconds?: number;
|
|
63
|
+
refreshTokenReuseGraceSeconds?: number;
|
|
64
|
+
tokenEncryptionSecret: string;
|
|
65
|
+
principalToSubject: (principal: TPrincipal) => TSubject | null | Promise<TSubject | null>;
|
|
66
|
+
subjectToPrincipal?: (subject: TSubject) => TPrincipal | null | Promise<TPrincipal | null>;
|
|
67
|
+
canAuthorize?: (principal: TPrincipal, client: OAuthClientInformation, params: McpAuthorizationParams) => boolean | Promise<boolean>;
|
|
68
|
+
canUseToken?: (principal: TPrincipal, row: McpTokenRow) => boolean | Promise<boolean>;
|
|
69
|
+
accessDeniedDescription?: string;
|
|
70
|
+
createAuthorizationCode?: () => string;
|
|
71
|
+
createAccessToken?: () => string;
|
|
72
|
+
createRefreshToken?: () => string;
|
|
73
|
+
now?: () => number;
|
|
74
|
+
};
|
|
75
|
+
export declare class McpAuthService<TPrincipal, TSubject extends Record<string, string> = Record<string, string>> {
|
|
76
|
+
private readonly options;
|
|
77
|
+
readonly skipLocalPkceValidation = false;
|
|
78
|
+
constructor(options: McpAuthServiceOptions<TPrincipal, TSubject>);
|
|
79
|
+
get clientsStore(): {
|
|
80
|
+
getClient: (clientId: string) => Promise<OAuthClientInformation | undefined>;
|
|
81
|
+
registerClient: (client: OAuthClientRegistration) => Promise<OAuthClientInformation>;
|
|
82
|
+
};
|
|
83
|
+
authorize(client: OAuthClientInformation, params: McpAuthorizationParams, request: Request): Promise<McpAuthorizeResult>;
|
|
84
|
+
challengeForAuthorizationCode(client: OAuthClientInformation, authorizationCode: string): Promise<string>;
|
|
85
|
+
exchangeAuthorizationCode(client: OAuthClientInformation, authorizationCode: string, _codeVerifier?: string, redirectUri?: string, resource?: URL): Promise<OAuthTokens>;
|
|
86
|
+
exchangeRefreshToken(client: OAuthClientInformation, refreshToken: string, scopes?: string[], resource?: URL): Promise<OAuthTokens>;
|
|
87
|
+
verifyAccessToken(token: string): Promise<McpAuthInfo<TSubject>>;
|
|
88
|
+
revokeToken(client: OAuthClientInformation, request: {
|
|
89
|
+
token: string;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
private validAuthorizationCode;
|
|
92
|
+
private issueTokens;
|
|
93
|
+
private issueTokenSet;
|
|
94
|
+
private cachedRefreshResponse;
|
|
95
|
+
private defaultScopes;
|
|
96
|
+
private loginRedirect;
|
|
97
|
+
private now;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { HttpError, requireAdminToken } from "./http.js";
|
|
2
|
+
import { createOAuthRedirect, decryptOAuthTokenResponse, encryptOAuthTokenResponse, issueMcpOAuthTokenSet, parseOAuthJsonArray, } from "./oauth.js";
|
|
3
|
+
import { verifySession } from "./session.js";
|
|
4
|
+
export class AuthSystem {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
async sessionFromRequest(request) {
|
|
10
|
+
const config = this.options.session;
|
|
11
|
+
if (!config)
|
|
12
|
+
return null;
|
|
13
|
+
const token = getCookie(request, config.cookie.name);
|
|
14
|
+
const payload = config.verifyToken
|
|
15
|
+
? await config.verifyToken(token, config.secret, request)
|
|
16
|
+
: await verifySession(token, config.secret);
|
|
17
|
+
if (!payload)
|
|
18
|
+
return null;
|
|
19
|
+
return config.verify ? await config.verify(payload) : payload;
|
|
20
|
+
}
|
|
21
|
+
async contextFromRequest(request) {
|
|
22
|
+
const session = await this.sessionFromRequest(request);
|
|
23
|
+
const principal = session && this.options.loadPrincipal ? await this.options.loadPrincipal(session, request) : null;
|
|
24
|
+
return { request, session, principal };
|
|
25
|
+
}
|
|
26
|
+
async requirePrincipal(request) {
|
|
27
|
+
const context = await this.contextFromRequest(request);
|
|
28
|
+
if (!context.principal)
|
|
29
|
+
throw new HttpError(401, "Authentication required.", "authentication_required");
|
|
30
|
+
return context.principal;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createAuthSystem(options) {
|
|
34
|
+
return new AuthSystem(options);
|
|
35
|
+
}
|
|
36
|
+
export function getCookie(request, name) {
|
|
37
|
+
const cookie = request.headers.get("cookie");
|
|
38
|
+
if (!cookie)
|
|
39
|
+
return undefined;
|
|
40
|
+
for (const part of cookie.split(";")) {
|
|
41
|
+
const [rawName, ...rawValue] = part.trim().split("=");
|
|
42
|
+
if (rawName === name)
|
|
43
|
+
return decodeURIComponent(rawValue.join("="));
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
export function requireAdminRequest(request, expectedToken) {
|
|
48
|
+
requireAdminToken(request, expectedToken);
|
|
49
|
+
}
|
|
50
|
+
const DEFAULT_SCOPE = "mcp:read";
|
|
51
|
+
const DEFAULT_AUTH_CODE_TTL_SECONDS = 10 * 60;
|
|
52
|
+
const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
|
|
53
|
+
const DEFAULT_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
54
|
+
const DEFAULT_REFRESH_TOKEN_REUSE_GRACE_SECONDS = 10 * 60;
|
|
55
|
+
export class McpAuthService {
|
|
56
|
+
options;
|
|
57
|
+
skipLocalPkceValidation = false;
|
|
58
|
+
constructor(options) {
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
get clientsStore() {
|
|
62
|
+
return {
|
|
63
|
+
getClient: (clientId) => this.options.repository.getClient(clientId),
|
|
64
|
+
registerClient: (client) => this.options.repository.registerClient(client),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async authorize(client, params, request) {
|
|
68
|
+
const principal = this.options.sessionAuth ? await this.options.sessionAuth.requirePrincipal(request).catch(() => null) : null;
|
|
69
|
+
if (!principal) {
|
|
70
|
+
return { kind: "redirect", location: this.loginRedirect(request), status: 302 };
|
|
71
|
+
}
|
|
72
|
+
const allowed = this.options.canAuthorize ? await this.options.canAuthorize(principal, client, params) : true;
|
|
73
|
+
const subject = allowed ? await this.options.principalToSubject(principal) : null;
|
|
74
|
+
if (!allowed || !subject) {
|
|
75
|
+
const redirect = createOAuthRedirect(params.redirectUri, {
|
|
76
|
+
error: "access_denied",
|
|
77
|
+
error_description: this.options.accessDeniedDescription ?? "Access denied.",
|
|
78
|
+
state: params.state,
|
|
79
|
+
});
|
|
80
|
+
return { kind: "redirect", location: redirect, status: 302 };
|
|
81
|
+
}
|
|
82
|
+
const code = this.options.createAuthorizationCode?.() ?? `mcpcode_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
83
|
+
await this.options.repository.createAuthorizationCode({
|
|
84
|
+
code,
|
|
85
|
+
clientId: client.client_id,
|
|
86
|
+
subject,
|
|
87
|
+
redirectUri: params.redirectUri,
|
|
88
|
+
scopes: params.scopes?.length ? params.scopes : this.defaultScopes(),
|
|
89
|
+
resource: params.resource?.href ?? null,
|
|
90
|
+
codeChallenge: params.codeChallenge,
|
|
91
|
+
expiresAt: this.now() + (this.options.authCodeTtlSeconds ?? DEFAULT_AUTH_CODE_TTL_SECONDS),
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
kind: "authorized",
|
|
95
|
+
code,
|
|
96
|
+
redirect: createOAuthRedirect(params.redirectUri, { code, state: params.state }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async challengeForAuthorizationCode(client, authorizationCode) {
|
|
100
|
+
const row = await this.validAuthorizationCode(client, authorizationCode);
|
|
101
|
+
return row.codeChallenge;
|
|
102
|
+
}
|
|
103
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, redirectUri, resource) {
|
|
104
|
+
const row = await this.validAuthorizationCode(client, authorizationCode);
|
|
105
|
+
if (redirectUri && row.redirectUri !== redirectUri)
|
|
106
|
+
throw invalidGrant("Redirect URI mismatch");
|
|
107
|
+
if (resource?.href && row.resource && row.resource !== resource.href)
|
|
108
|
+
throw invalidGrant("Resource mismatch");
|
|
109
|
+
await this.options.repository.consumeAuthorizationCode(row.authorizationCodeId);
|
|
110
|
+
return this.issueTokens({
|
|
111
|
+
clientId: row.clientId,
|
|
112
|
+
subject: row.subject,
|
|
113
|
+
scopes: parseOAuthJsonArray(row.scopesJson),
|
|
114
|
+
resource: row.resource ? new URL(row.resource) : resource,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
118
|
+
const row = await this.options.repository.getTokenByRefreshToken(refreshToken);
|
|
119
|
+
if (!row || row.clientId !== client.client_id)
|
|
120
|
+
throw invalidGrant("Invalid refresh token");
|
|
121
|
+
const cachedResponse = await this.cachedRefreshResponse(row);
|
|
122
|
+
if (cachedResponse)
|
|
123
|
+
return cachedResponse;
|
|
124
|
+
if (row.revokedAt)
|
|
125
|
+
throw invalidGrant("Invalid refresh token");
|
|
126
|
+
if (row.refreshExpiresAt && row.refreshExpiresAt < this.now())
|
|
127
|
+
throw invalidGrant("Refresh token expired");
|
|
128
|
+
const grantedScopes = parseOAuthJsonArray(row.scopesJson);
|
|
129
|
+
const requestedScopes = scopes?.length ? scopes.filter((scope) => grantedScopes.includes(scope)) : grantedScopes;
|
|
130
|
+
const issued = await this.issueTokenSet({
|
|
131
|
+
clientId: row.clientId,
|
|
132
|
+
subject: row.subject,
|
|
133
|
+
scopes: requestedScopes,
|
|
134
|
+
resource: resource ?? (row.resource ? new URL(row.resource) : undefined),
|
|
135
|
+
});
|
|
136
|
+
const encryptedResponse = await encryptOAuthTokenResponse(issued.tokens, this.options.tokenEncryptionSecret);
|
|
137
|
+
const claimedRotation = await this.options.repository.markTokenRotated({
|
|
138
|
+
tokenId: row.tokenId,
|
|
139
|
+
rotatedToTokenId: issued.tokenId,
|
|
140
|
+
refreshReuseExpiresAt: this.now() + (this.options.refreshTokenReuseGraceSeconds ?? DEFAULT_REFRESH_TOKEN_REUSE_GRACE_SECONDS),
|
|
141
|
+
rotatedResponseCiphertext: encryptedResponse.ciphertext,
|
|
142
|
+
rotatedResponseNonce: encryptedResponse.nonce,
|
|
143
|
+
});
|
|
144
|
+
if (claimedRotation)
|
|
145
|
+
return issued.tokens;
|
|
146
|
+
await this.options.repository.revokeToken(issued.tokenId);
|
|
147
|
+
const winner = await this.options.repository.getTokenByRefreshToken(refreshToken);
|
|
148
|
+
const winnerResponse = winner ? await this.cachedRefreshResponse(winner) : null;
|
|
149
|
+
if (winnerResponse)
|
|
150
|
+
return winnerResponse;
|
|
151
|
+
throw invalidGrant("Invalid refresh token");
|
|
152
|
+
}
|
|
153
|
+
async verifyAccessToken(token) {
|
|
154
|
+
const row = await this.options.repository.getTokenByAccessToken(token);
|
|
155
|
+
if (!row || row.revokedAt || row.accessExpiresAt < this.now())
|
|
156
|
+
throw new HttpError(401, "Invalid access token.", "invalid_access_token");
|
|
157
|
+
const subject = row.subject;
|
|
158
|
+
const principal = this.options.subjectToPrincipal ? await this.options.subjectToPrincipal(subject) : null;
|
|
159
|
+
if (this.options.subjectToPrincipal && !principal)
|
|
160
|
+
throw new HttpError(401, "Invalid access token.", "invalid_access_token");
|
|
161
|
+
if (principal && this.options.canUseToken && !(await this.options.canUseToken(principal, row))) {
|
|
162
|
+
throw new HttpError(403, "Token is not allowed.", "token_not_allowed");
|
|
163
|
+
}
|
|
164
|
+
await this.options.repository.touchAccessToken(row.tokenId);
|
|
165
|
+
return {
|
|
166
|
+
token,
|
|
167
|
+
clientId: row.clientId,
|
|
168
|
+
scopes: parseOAuthJsonArray(row.scopesJson),
|
|
169
|
+
expiresAt: row.accessExpiresAt,
|
|
170
|
+
resource: row.resource ? new URL(row.resource) : undefined,
|
|
171
|
+
subject,
|
|
172
|
+
extra: { ...subject },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async revokeToken(client, request) {
|
|
176
|
+
const accessRow = await this.options.repository.getTokenByAccessToken(request.token);
|
|
177
|
+
if (accessRow && accessRow.clientId === client.client_id) {
|
|
178
|
+
await this.options.repository.revokeToken(accessRow.tokenId);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const refreshRow = await this.options.repository.getTokenByRefreshToken(request.token);
|
|
182
|
+
if (refreshRow && refreshRow.clientId === client.client_id)
|
|
183
|
+
await this.options.repository.revokeToken(refreshRow.tokenId);
|
|
184
|
+
}
|
|
185
|
+
async validAuthorizationCode(client, authorizationCode) {
|
|
186
|
+
const row = await this.options.repository.getAuthorizationCode(authorizationCode);
|
|
187
|
+
if (!row || row.clientId !== client.client_id || row.usedAt || row.expiresAt < this.now()) {
|
|
188
|
+
throw invalidGrant("Invalid authorization code");
|
|
189
|
+
}
|
|
190
|
+
return row;
|
|
191
|
+
}
|
|
192
|
+
async issueTokens(input) {
|
|
193
|
+
return (await this.issueTokenSet(input)).tokens;
|
|
194
|
+
}
|
|
195
|
+
async issueTokenSet(input) {
|
|
196
|
+
return issueMcpOAuthTokenSet(this.options.repository, {
|
|
197
|
+
clientId: input.clientId,
|
|
198
|
+
subject: input.subject,
|
|
199
|
+
scopes: input.scopes,
|
|
200
|
+
resource: input.resource,
|
|
201
|
+
}, {
|
|
202
|
+
accessTokenTtlSeconds: this.options.accessTokenTtlSeconds ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS,
|
|
203
|
+
refreshTokenTtlSeconds: this.options.refreshTokenTtlSeconds ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS,
|
|
204
|
+
createAccessToken: this.options.createAccessToken,
|
|
205
|
+
createRefreshToken: this.options.createRefreshToken,
|
|
206
|
+
now: this.options.now,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async cachedRefreshResponse(row) {
|
|
210
|
+
if (!row.rotatedResponseCiphertext || !row.rotatedResponseNonce || !row.refreshReuseExpiresAt || row.refreshReuseExpiresAt < this.now())
|
|
211
|
+
return null;
|
|
212
|
+
return decryptOAuthTokenResponse(row.rotatedResponseCiphertext, row.rotatedResponseNonce, this.options.tokenEncryptionSecret);
|
|
213
|
+
}
|
|
214
|
+
defaultScopes() {
|
|
215
|
+
return this.options.defaultScopes?.length ? this.options.defaultScopes : [DEFAULT_SCOPE];
|
|
216
|
+
}
|
|
217
|
+
loginRedirect(request) {
|
|
218
|
+
const loginPath = this.options.loginPath ?? "/login";
|
|
219
|
+
const url = new URL(loginPath, request.url);
|
|
220
|
+
url.searchParams.set("next", request.url);
|
|
221
|
+
return `${url.pathname}${url.search}`;
|
|
222
|
+
}
|
|
223
|
+
now() {
|
|
224
|
+
return this.options.now?.() ?? Math.floor(Date.now() / 1000);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function invalidGrant(message) {
|
|
228
|
+
const error = new Error(message);
|
|
229
|
+
error.name = "InvalidGrantError";
|
|
230
|
+
return error;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/hono.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthSystem, McpAuthService, McpAuthorizationParams } from "./auth.js";
|
|
2
|
+
import type { OAuthClientInformation } from "./oauth.js";
|
|
3
|
+
export type HonoContextLike<TEnv = unknown> = {
|
|
4
|
+
req: {
|
|
5
|
+
raw: Request;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
env?: TEnv;
|
|
9
|
+
set?: (key: string, value: unknown) => void;
|
|
10
|
+
get?: (key: string) => unknown;
|
|
11
|
+
json: (value: unknown, status?: number) => Response;
|
|
12
|
+
redirect: (location: string, status?: number) => Response;
|
|
13
|
+
res?: Response;
|
|
14
|
+
};
|
|
15
|
+
export type HonoNext = () => Promise<void>;
|
|
16
|
+
export declare function honoAuthMiddleware<TPrincipal, TSession>(auth: AuthSystem<TPrincipal, TSession>, options?: {
|
|
17
|
+
contextKey?: string;
|
|
18
|
+
requirePrincipal?: boolean;
|
|
19
|
+
}): (context: HonoContextLike, next: HonoNext) => Promise<Response | void>;
|
|
20
|
+
export declare function honoRequirePrincipal<TPrincipal, TSession>(context: HonoContextLike, auth: AuthSystem<TPrincipal, TSession>): Promise<TPrincipal>;
|
|
21
|
+
export declare function honoMcpAuthorize<TPrincipal, TSubject extends Record<string, string>>(context: HonoContextLike, service: McpAuthService<TPrincipal, TSubject>, client: OAuthClientInformation, params: McpAuthorizationParams): Promise<Response>;
|
|
22
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,MAAM,eAAe,CAAC,IAAI,GAAG,OAAO,IAAI;IAC5C,GAAG,EAAE;QACH,GAAG,EAAE,OAAO,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/B,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IACpD,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;IAC1D,GAAG,CAAC,EAAE,QAAQ,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3C,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,QAAQ,EACrD,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,EACtC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAChE,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAaxE;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,QAAQ,EAC7D,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,GACrC,OAAO,CAAC,UAAU,CAAC,CAErB;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACxF,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,EAC7C,MAAM,EAAE,sBAAsB,EAC9B,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAInB"}
|
package/dist/hono.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { errorResponse, HttpError } from "./http.js";
|
|
2
|
+
export function honoAuthMiddleware(auth, options = {}) {
|
|
3
|
+
return async (context, next) => {
|
|
4
|
+
try {
|
|
5
|
+
const authContext = await auth.contextFromRequest(context.req.raw);
|
|
6
|
+
if (options.requirePrincipal && !authContext.principal) {
|
|
7
|
+
throw new HttpError(401, "Authentication required.", "authentication_required");
|
|
8
|
+
}
|
|
9
|
+
context.set?.(options.contextKey ?? "auth", authContext);
|
|
10
|
+
await next();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return errorResponse(error);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function honoRequirePrincipal(context, auth) {
|
|
18
|
+
return auth.requirePrincipal(context.req.raw);
|
|
19
|
+
}
|
|
20
|
+
export async function honoMcpAuthorize(context, service, client, params) {
|
|
21
|
+
const result = await service.authorize(client, params, context.req.raw);
|
|
22
|
+
if (result.kind === "redirect")
|
|
23
|
+
return context.redirect(result.location, result.status ?? 302);
|
|
24
|
+
return context.redirect(result.redirect, 302);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=hono.js.map
|
package/dist/hono.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAkBrD,MAAM,UAAU,kBAAkB,CAChC,IAAsC,EACtC,UAA+D,EAAE;IAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnE,IAAI,OAAO,CAAC,gBAAgB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBACvD,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,0BAA0B,EAAE,yBAAyB,CAAC,CAAC;YAClF,CAAC;YACD,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,EAAE,WAAW,CAAC,CAAC;YACzD,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAwB,EACxB,IAAsC;IAEtC,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAwB,EACxB,OAA6C,EAC7C,MAA8B,EAC9B,MAA8B;IAE9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;IAC/F,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./api.js";
|
|
2
2
|
export * from "./ai.js";
|
|
3
|
+
export * from "./auth.js";
|
|
3
4
|
export * from "./billing.js";
|
|
4
5
|
export * from "./cloudflare.js";
|
|
5
6
|
export * from "./crypto.js";
|
|
@@ -7,6 +8,7 @@ export * from "./d1.js";
|
|
|
7
8
|
export * from "./encoding.js";
|
|
8
9
|
export * from "./email.js";
|
|
9
10
|
export * from "./env.js";
|
|
11
|
+
export * from "./hono.js";
|
|
10
12
|
export * from "./http.js";
|
|
11
13
|
export * from "./ids.js";
|
|
12
14
|
export * from "./jobs.js";
|
|
@@ -17,6 +19,7 @@ export * from "./pg.js";
|
|
|
17
19
|
export * from "./pg-kysely.js";
|
|
18
20
|
export * from "./r2-json.js";
|
|
19
21
|
export * from "./rate-limit.js";
|
|
22
|
+
export * from "./react-router.js";
|
|
20
23
|
export * from "./seo.js";
|
|
21
24
|
export * from "./session.js";
|
|
22
25
|
export * from "./testing.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./api.js";
|
|
2
2
|
export * from "./ai.js";
|
|
3
|
+
export * from "./auth.js";
|
|
3
4
|
export * from "./billing.js";
|
|
4
5
|
export * from "./cloudflare.js";
|
|
5
6
|
export * from "./crypto.js";
|
|
@@ -7,6 +8,7 @@ export * from "./d1.js";
|
|
|
7
8
|
export * from "./encoding.js";
|
|
8
9
|
export * from "./email.js";
|
|
9
10
|
export * from "./env.js";
|
|
11
|
+
export * from "./hono.js";
|
|
10
12
|
export * from "./http.js";
|
|
11
13
|
export * from "./ids.js";
|
|
12
14
|
export * from "./jobs.js";
|
|
@@ -17,6 +19,7 @@ export * from "./pg.js";
|
|
|
17
19
|
export * from "./pg-kysely.js";
|
|
18
20
|
export * from "./r2-json.js";
|
|
19
21
|
export * from "./rate-limit.js";
|
|
22
|
+
export * from "./react-router.js";
|
|
20
23
|
export * from "./seo.js";
|
|
21
24
|
export * from "./session.js";
|
|
22
25
|
export * from "./testing.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuthSystem, McpAuthService, McpAuthorizationParams } from "./auth.js";
|
|
2
|
+
import type { OAuthClientInformation } from "./oauth.js";
|
|
3
|
+
export type ReactRouterRequestArgs = {
|
|
4
|
+
request: Request;
|
|
5
|
+
params?: Record<string, string | undefined>;
|
|
6
|
+
context?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare function reactRouterAuthContext<TPrincipal, TSession>(auth: AuthSystem<TPrincipal, TSession>, args: ReactRouterRequestArgs): Promise<import("./auth.js").AuthContext<TPrincipal, TSession>>;
|
|
9
|
+
export declare function reactRouterRequirePrincipal<TPrincipal, TSession>(auth: AuthSystem<TPrincipal, TSession>, args: ReactRouterRequestArgs): Promise<TPrincipal>;
|
|
10
|
+
export declare function reactRouterJson(value: unknown, init?: ResponseInit): Response;
|
|
11
|
+
export declare function reactRouterRedirect(location: string, status?: number): Response;
|
|
12
|
+
export declare function reactRouterError(error: unknown): Response;
|
|
13
|
+
export declare function reactRouterMcpAuthorize<TPrincipal, TSubject extends Record<string, string>>(args: ReactRouterRequestArgs, service: McpAuthService<TPrincipal, TSubject>, client: OAuthClientInformation, params: McpAuthorizationParams): Promise<Response>;
|
|
14
|
+
//# sourceMappingURL=react-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,QAAQ,EAC/D,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,EACtC,IAAI,EAAE,sBAAsB,kEAG7B;AAED,wBAAsB,2BAA2B,CAAC,UAAU,EAAE,QAAQ,EACpE,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,EACtC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAErB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,YAAiB,GAAG,QAAQ,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK5E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAOzD;AAED,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/F,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,EAC7C,MAAM,EAAE,sBAAsB,EAC9B,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAGnB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { HttpError, jsonResponse } from "./http.js";
|
|
2
|
+
export async function reactRouterAuthContext(auth, args) {
|
|
3
|
+
return auth.contextFromRequest(args.request);
|
|
4
|
+
}
|
|
5
|
+
export async function reactRouterRequirePrincipal(auth, args) {
|
|
6
|
+
return auth.requirePrincipal(args.request);
|
|
7
|
+
}
|
|
8
|
+
export function reactRouterJson(value, init = {}) {
|
|
9
|
+
return jsonResponse(value, init);
|
|
10
|
+
}
|
|
11
|
+
export function reactRouterRedirect(location, status = 302) {
|
|
12
|
+
return new Response(null, {
|
|
13
|
+
status,
|
|
14
|
+
headers: { location },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function reactRouterError(error) {
|
|
18
|
+
if (error instanceof Response)
|
|
19
|
+
return error;
|
|
20
|
+
if (error instanceof HttpError) {
|
|
21
|
+
return jsonResponse({ error: { code: error.code, message: error.message } }, { status: error.status });
|
|
22
|
+
}
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
return jsonResponse({ error: { code: "internal_error", message } }, { status: 500 });
|
|
25
|
+
}
|
|
26
|
+
export async function reactRouterMcpAuthorize(args, service, client, params) {
|
|
27
|
+
const result = await service.authorize(client, params, args.request);
|
|
28
|
+
return reactRouterRedirect(result.kind === "redirect" ? result.location : result.redirect, result.kind === "redirect" ? (result.status ?? 302) : 302);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=react-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-router.js","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AASpD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAsC,EACtC,IAA4B;IAE5B,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,IAAsC,EACtC,IAA4B;IAE5B,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,OAAqB,EAAE;IACrE,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,MAAM,GAAG,GAAG;IAChE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM;QACN,OAAO,EAAE,EAAE,QAAQ,EAAE;KACtB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,KAAK,YAAY,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;QAC/B,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzG,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAA4B,EAC5B,OAA6C,EAC7C,MAA8B,EAC9B,MAA8B;IAE9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,OAAO,mBAAmB,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACxJ,CAAC"}
|
package/docs/evaluation.md
CHANGED
|
@@ -12,6 +12,7 @@ This package was seeded from repeated infrastructure found across Erik/Q32 TypeS
|
|
|
12
12
|
| JSON responses, bearer/admin token checks, request body parsing | `http` | Replace repeated Worker/Hono-adjacent HTTP utility functions. |
|
|
13
13
|
| Prefixed IDs, random tokens, base64url, SHA-256 | `ids` | Replace local `createId`, token, digest-key, and hash helpers. |
|
|
14
14
|
| Signed session tokens and cookies | `session` | Replace small HMAC session implementations where full auth frameworks are unnecessary. |
|
|
15
|
+
| Auth/session/MCP authorization policy | `auth` | Keep principal lookup, session verification, admin checks, OAuth authorization-code exchange, token verification, and refresh rotation independent of Hono, React Router, or any single app. |
|
|
15
16
|
| Credential or provider-secret encryption | `crypto` | Replace local AES-GCM JSON encryption helpers backed by WebCrypto. |
|
|
16
17
|
| D1-like database typing and explicit migrations | `d1` | Replace duplicated `D1DatabaseLike` types and simple migration runners. |
|
|
17
18
|
| Postgres migration and JSON helpers | `pg` | Replace small pg migration scripts while still using official `pg` or `postgres` clients in apps. |
|
|
@@ -23,8 +24,9 @@ This package was seeded from repeated infrastructure found across Erik/Q32 TypeS
|
|
|
23
24
|
| Email provider boundary | `email` | Standardize provider-independent send input/result shapes and address helpers. |
|
|
24
25
|
| Billing plan/status checks | `billing` | Replace local plan rank and active subscription status helpers around Stripe-backed apps. |
|
|
25
26
|
| Worker test helpers | `testing` | Replace small fake Queue/R2 helpers and JSON response assertions in unit tests; complements workerd/Miniflare integration tests. |
|
|
26
|
-
| OAuth/MCP metadata | `oauth`, `mcp` | Replace repeated discovery metadata
|
|
27
|
+
| OAuth/MCP metadata and storage | `oauth`, `mcp` | Replace repeated discovery metadata, API-to-tool descriptors, and configurable D1 OAuth client/code/token repositories. |
|
|
27
28
|
| API operation registries | `api` | Replace local operation registries used for OpenAPI, admin APIs, and MCP exposure. |
|
|
29
|
+
| Framework request adapters | `hono`, `react-router` | Translate Hono middleware and React Router loader/action requests into the same framework-neutral auth/API services. |
|
|
28
30
|
|
|
29
31
|
## Current Fit
|
|
30
32
|
|
|
@@ -37,14 +39,13 @@ Strong first replacement candidates:
|
|
|
37
39
|
- D1 jobs in `travelerideas`, `bizsnipe`, `logtura`, and smaller domain apps.
|
|
38
40
|
- signed session, ID, token, and base64url helpers in `zura`, `relin`, `getflight`, and `adgiro`.
|
|
39
41
|
- API operation metadata from `bce.email` and `relin`.
|
|
40
|
-
- OAuth/MCP metadata from `getflight`, `captcha`, `ipogrid`, `relin`, and `bce.email`.
|
|
42
|
+
- OAuth/MCP metadata and auth/token flow from `getflight`, `captcha`, `ipogrid`, `relin`, and `bce.email`.
|
|
41
43
|
|
|
42
44
|
## Not Yet Complete
|
|
43
45
|
|
|
44
46
|
The package intentionally does not yet replace:
|
|
45
47
|
|
|
46
|
-
-
|
|
47
|
-
- full OAuth authorization-code and refresh-token stores
|
|
48
|
+
- complete app-specific login/account repositories
|
|
48
49
|
- Stripe billing repositories
|
|
49
50
|
- Postgres job runners and migration orchestration
|
|
50
51
|
- email provider clients
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@q32/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Shared TypeScript primitives for Q32 Cloudflare Worker projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"types": "./dist/ai.d.ts",
|
|
29
29
|
"default": "./dist/ai.js"
|
|
30
30
|
},
|
|
31
|
+
"./auth": {
|
|
32
|
+
"types": "./dist/auth.d.ts",
|
|
33
|
+
"default": "./dist/auth.js"
|
|
34
|
+
},
|
|
31
35
|
"./billing": {
|
|
32
36
|
"types": "./dist/billing.d.ts",
|
|
33
37
|
"default": "./dist/billing.js"
|
|
@@ -60,6 +64,10 @@
|
|
|
60
64
|
"types": "./dist/hash.d.ts",
|
|
61
65
|
"default": "./dist/hash.js"
|
|
62
66
|
},
|
|
67
|
+
"./hono": {
|
|
68
|
+
"types": "./dist/hono.d.ts",
|
|
69
|
+
"default": "./dist/hono.js"
|
|
70
|
+
},
|
|
63
71
|
"./http": {
|
|
64
72
|
"types": "./dist/http.d.ts",
|
|
65
73
|
"default": "./dist/http.js"
|
|
@@ -100,6 +108,10 @@
|
|
|
100
108
|
"types": "./dist/rate-limit.d.ts",
|
|
101
109
|
"default": "./dist/rate-limit.js"
|
|
102
110
|
},
|
|
111
|
+
"./react-router": {
|
|
112
|
+
"types": "./dist/react-router.d.ts",
|
|
113
|
+
"default": "./dist/react-router.js"
|
|
114
|
+
},
|
|
103
115
|
"./seo": {
|
|
104
116
|
"types": "./dist/seo.d.ts",
|
|
105
117
|
"default": "./dist/seo.js"
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { HttpError, requireAdminToken } from "./http.js";
|
|
2
|
+
import {
|
|
3
|
+
createOAuthRedirect,
|
|
4
|
+
decryptOAuthTokenResponse,
|
|
5
|
+
encryptOAuthTokenResponse,
|
|
6
|
+
issueMcpOAuthTokenSet,
|
|
7
|
+
type McpOAuthRepository,
|
|
8
|
+
type McpTokenRow,
|
|
9
|
+
type OAuthClientInformation,
|
|
10
|
+
type OAuthClientRegistration,
|
|
11
|
+
type OAuthTokens,
|
|
12
|
+
parseOAuthJsonArray,
|
|
13
|
+
} from "./oauth.js";
|
|
14
|
+
import { verifySession } from "./session.js";
|
|
15
|
+
|
|
16
|
+
export type CookieOptions = {
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type SessionAuthOptions<TSession> = {
|
|
21
|
+
secret: string;
|
|
22
|
+
cookie: CookieOptions;
|
|
23
|
+
verifyToken?: (token: string | undefined, secret: string, request: Request) => Promise<TSession | null> | TSession | null;
|
|
24
|
+
verify?: (payload: TSession) => Promise<TSession | null> | TSession | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type AuthContext<TPrincipal = unknown, TSession = unknown> = {
|
|
28
|
+
request: Request;
|
|
29
|
+
session: TSession | null;
|
|
30
|
+
principal: TPrincipal | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type AuthSystemOptions<TPrincipal, TSession> = {
|
|
34
|
+
session?: SessionAuthOptions<TSession>;
|
|
35
|
+
loadPrincipal?: (session: TSession, request: Request) => Promise<TPrincipal | null> | TPrincipal | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class AuthSystem<TPrincipal = unknown, TSession = unknown> {
|
|
39
|
+
constructor(private readonly options: AuthSystemOptions<TPrincipal, TSession>) {}
|
|
40
|
+
|
|
41
|
+
async sessionFromRequest(request: Request): Promise<TSession | null> {
|
|
42
|
+
const config = this.options.session;
|
|
43
|
+
if (!config) return null;
|
|
44
|
+
const token = getCookie(request, config.cookie.name);
|
|
45
|
+
const payload = config.verifyToken
|
|
46
|
+
? await config.verifyToken(token, config.secret, request)
|
|
47
|
+
: await verifySession<TSession>(token, config.secret);
|
|
48
|
+
if (!payload) return null;
|
|
49
|
+
return config.verify ? await config.verify(payload) : payload;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async contextFromRequest(request: Request): Promise<AuthContext<TPrincipal, TSession>> {
|
|
53
|
+
const session = await this.sessionFromRequest(request);
|
|
54
|
+
const principal = session && this.options.loadPrincipal ? await this.options.loadPrincipal(session, request) : null;
|
|
55
|
+
return { request, session, principal };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async requirePrincipal(request: Request): Promise<TPrincipal> {
|
|
59
|
+
const context = await this.contextFromRequest(request);
|
|
60
|
+
if (!context.principal) throw new HttpError(401, "Authentication required.", "authentication_required");
|
|
61
|
+
return context.principal;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createAuthSystem<TPrincipal = unknown, TSession = unknown>(
|
|
66
|
+
options: AuthSystemOptions<TPrincipal, TSession>,
|
|
67
|
+
): AuthSystem<TPrincipal, TSession> {
|
|
68
|
+
return new AuthSystem(options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getCookie(request: Request, name: string): string | undefined {
|
|
72
|
+
const cookie = request.headers.get("cookie");
|
|
73
|
+
if (!cookie) return undefined;
|
|
74
|
+
for (const part of cookie.split(";")) {
|
|
75
|
+
const [rawName, ...rawValue] = part.trim().split("=");
|
|
76
|
+
if (rawName === name) return decodeURIComponent(rawValue.join("="));
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function requireAdminRequest(request: Request, expectedToken: string | undefined): void {
|
|
82
|
+
requireAdminToken(request, expectedToken);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type McpAuthInfo<TSubject extends Record<string, string> = Record<string, string>> = {
|
|
86
|
+
token: string;
|
|
87
|
+
clientId: string;
|
|
88
|
+
scopes: string[];
|
|
89
|
+
expiresAt: number;
|
|
90
|
+
resource?: URL;
|
|
91
|
+
subject: TSubject;
|
|
92
|
+
extra?: Record<string, unknown>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type McpAuthorizationParams = {
|
|
96
|
+
redirectUri: string;
|
|
97
|
+
state?: string;
|
|
98
|
+
scopes?: string[];
|
|
99
|
+
resource?: URL;
|
|
100
|
+
codeChallenge: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type McpAuthorizeResult =
|
|
104
|
+
| { kind: "redirect"; location: string; status?: number }
|
|
105
|
+
| { kind: "authorized"; code: string; redirect: string };
|
|
106
|
+
|
|
107
|
+
export type McpAuthServiceOptions<TPrincipal, TSubject extends Record<string, string>> = {
|
|
108
|
+
repository: McpOAuthRepository;
|
|
109
|
+
sessionAuth?: AuthSystem<TPrincipal, unknown>;
|
|
110
|
+
loginPath?: string;
|
|
111
|
+
defaultScopes?: string[];
|
|
112
|
+
authCodeTtlSeconds?: number;
|
|
113
|
+
accessTokenTtlSeconds?: number;
|
|
114
|
+
refreshTokenTtlSeconds?: number;
|
|
115
|
+
refreshTokenReuseGraceSeconds?: number;
|
|
116
|
+
tokenEncryptionSecret: string;
|
|
117
|
+
principalToSubject: (principal: TPrincipal) => TSubject | null | Promise<TSubject | null>;
|
|
118
|
+
subjectToPrincipal?: (subject: TSubject) => TPrincipal | null | Promise<TPrincipal | null>;
|
|
119
|
+
canAuthorize?: (principal: TPrincipal, client: OAuthClientInformation, params: McpAuthorizationParams) => boolean | Promise<boolean>;
|
|
120
|
+
canUseToken?: (principal: TPrincipal, row: McpTokenRow) => boolean | Promise<boolean>;
|
|
121
|
+
accessDeniedDescription?: string;
|
|
122
|
+
createAuthorizationCode?: () => string;
|
|
123
|
+
createAccessToken?: () => string;
|
|
124
|
+
createRefreshToken?: () => string;
|
|
125
|
+
now?: () => number;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const DEFAULT_SCOPE = "mcp:read";
|
|
129
|
+
const DEFAULT_AUTH_CODE_TTL_SECONDS = 10 * 60;
|
|
130
|
+
const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
|
|
131
|
+
const DEFAULT_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
132
|
+
const DEFAULT_REFRESH_TOKEN_REUSE_GRACE_SECONDS = 10 * 60;
|
|
133
|
+
|
|
134
|
+
export class McpAuthService<TPrincipal, TSubject extends Record<string, string> = Record<string, string>> {
|
|
135
|
+
readonly skipLocalPkceValidation = false;
|
|
136
|
+
|
|
137
|
+
constructor(private readonly options: McpAuthServiceOptions<TPrincipal, TSubject>) {}
|
|
138
|
+
|
|
139
|
+
get clientsStore(): {
|
|
140
|
+
getClient: (clientId: string) => Promise<OAuthClientInformation | undefined>;
|
|
141
|
+
registerClient: (client: OAuthClientRegistration) => Promise<OAuthClientInformation>;
|
|
142
|
+
} {
|
|
143
|
+
return {
|
|
144
|
+
getClient: (clientId: string) => this.options.repository.getClient(clientId),
|
|
145
|
+
registerClient: (client: OAuthClientRegistration) => this.options.repository.registerClient(client),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async authorize(client: OAuthClientInformation, params: McpAuthorizationParams, request: Request): Promise<McpAuthorizeResult> {
|
|
150
|
+
const principal = this.options.sessionAuth ? await this.options.sessionAuth.requirePrincipal(request).catch(() => null) : null;
|
|
151
|
+
if (!principal) {
|
|
152
|
+
return { kind: "redirect", location: this.loginRedirect(request), status: 302 };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const allowed = this.options.canAuthorize ? await this.options.canAuthorize(principal, client, params) : true;
|
|
156
|
+
const subject = allowed ? await this.options.principalToSubject(principal) : null;
|
|
157
|
+
if (!allowed || !subject) {
|
|
158
|
+
const redirect = createOAuthRedirect(params.redirectUri, {
|
|
159
|
+
error: "access_denied",
|
|
160
|
+
error_description: this.options.accessDeniedDescription ?? "Access denied.",
|
|
161
|
+
state: params.state,
|
|
162
|
+
});
|
|
163
|
+
return { kind: "redirect", location: redirect, status: 302 };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const code = this.options.createAuthorizationCode?.() ?? `mcpcode_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
167
|
+
await this.options.repository.createAuthorizationCode({
|
|
168
|
+
code,
|
|
169
|
+
clientId: client.client_id,
|
|
170
|
+
subject,
|
|
171
|
+
redirectUri: params.redirectUri,
|
|
172
|
+
scopes: params.scopes?.length ? params.scopes : this.defaultScopes(),
|
|
173
|
+
resource: params.resource?.href ?? null,
|
|
174
|
+
codeChallenge: params.codeChallenge,
|
|
175
|
+
expiresAt: this.now() + (this.options.authCodeTtlSeconds ?? DEFAULT_AUTH_CODE_TTL_SECONDS),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
kind: "authorized",
|
|
180
|
+
code,
|
|
181
|
+
redirect: createOAuthRedirect(params.redirectUri, { code, state: params.state }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async challengeForAuthorizationCode(client: OAuthClientInformation, authorizationCode: string): Promise<string> {
|
|
186
|
+
const row = await this.validAuthorizationCode(client, authorizationCode);
|
|
187
|
+
return row.codeChallenge;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async exchangeAuthorizationCode(
|
|
191
|
+
client: OAuthClientInformation,
|
|
192
|
+
authorizationCode: string,
|
|
193
|
+
_codeVerifier?: string,
|
|
194
|
+
redirectUri?: string,
|
|
195
|
+
resource?: URL,
|
|
196
|
+
): Promise<OAuthTokens> {
|
|
197
|
+
const row = await this.validAuthorizationCode(client, authorizationCode);
|
|
198
|
+
if (redirectUri && row.redirectUri !== redirectUri) throw invalidGrant("Redirect URI mismatch");
|
|
199
|
+
if (resource?.href && row.resource && row.resource !== resource.href) throw invalidGrant("Resource mismatch");
|
|
200
|
+
|
|
201
|
+
await this.options.repository.consumeAuthorizationCode(row.authorizationCodeId);
|
|
202
|
+
return this.issueTokens({
|
|
203
|
+
clientId: row.clientId,
|
|
204
|
+
subject: row.subject as TSubject,
|
|
205
|
+
scopes: parseOAuthJsonArray(row.scopesJson),
|
|
206
|
+
resource: row.resource ? new URL(row.resource) : resource,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async exchangeRefreshToken(client: OAuthClientInformation, refreshToken: string, scopes?: string[], resource?: URL): Promise<OAuthTokens> {
|
|
211
|
+
const row = await this.options.repository.getTokenByRefreshToken(refreshToken);
|
|
212
|
+
if (!row || row.clientId !== client.client_id) throw invalidGrant("Invalid refresh token");
|
|
213
|
+
const cachedResponse = await this.cachedRefreshResponse(row);
|
|
214
|
+
if (cachedResponse) return cachedResponse;
|
|
215
|
+
if (row.revokedAt) throw invalidGrant("Invalid refresh token");
|
|
216
|
+
if (row.refreshExpiresAt && row.refreshExpiresAt < this.now()) throw invalidGrant("Refresh token expired");
|
|
217
|
+
|
|
218
|
+
const grantedScopes = parseOAuthJsonArray(row.scopesJson);
|
|
219
|
+
const requestedScopes = scopes?.length ? scopes.filter((scope) => grantedScopes.includes(scope)) : grantedScopes;
|
|
220
|
+
const issued = await this.issueTokenSet({
|
|
221
|
+
clientId: row.clientId,
|
|
222
|
+
subject: row.subject as TSubject,
|
|
223
|
+
scopes: requestedScopes,
|
|
224
|
+
resource: resource ?? (row.resource ? new URL(row.resource) : undefined),
|
|
225
|
+
});
|
|
226
|
+
const encryptedResponse = await encryptOAuthTokenResponse(issued.tokens, this.options.tokenEncryptionSecret);
|
|
227
|
+
const claimedRotation = await this.options.repository.markTokenRotated({
|
|
228
|
+
tokenId: row.tokenId,
|
|
229
|
+
rotatedToTokenId: issued.tokenId,
|
|
230
|
+
refreshReuseExpiresAt: this.now() + (this.options.refreshTokenReuseGraceSeconds ?? DEFAULT_REFRESH_TOKEN_REUSE_GRACE_SECONDS),
|
|
231
|
+
rotatedResponseCiphertext: encryptedResponse.ciphertext,
|
|
232
|
+
rotatedResponseNonce: encryptedResponse.nonce,
|
|
233
|
+
});
|
|
234
|
+
if (claimedRotation) return issued.tokens;
|
|
235
|
+
|
|
236
|
+
await this.options.repository.revokeToken(issued.tokenId);
|
|
237
|
+
const winner = await this.options.repository.getTokenByRefreshToken(refreshToken);
|
|
238
|
+
const winnerResponse = winner ? await this.cachedRefreshResponse(winner) : null;
|
|
239
|
+
if (winnerResponse) return winnerResponse;
|
|
240
|
+
throw invalidGrant("Invalid refresh token");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async verifyAccessToken(token: string): Promise<McpAuthInfo<TSubject>> {
|
|
244
|
+
const row = await this.options.repository.getTokenByAccessToken(token);
|
|
245
|
+
if (!row || row.revokedAt || row.accessExpiresAt < this.now()) throw new HttpError(401, "Invalid access token.", "invalid_access_token");
|
|
246
|
+
const subject = row.subject as TSubject;
|
|
247
|
+
const principal = this.options.subjectToPrincipal ? await this.options.subjectToPrincipal(subject) : null;
|
|
248
|
+
if (this.options.subjectToPrincipal && !principal) throw new HttpError(401, "Invalid access token.", "invalid_access_token");
|
|
249
|
+
if (principal && this.options.canUseToken && !(await this.options.canUseToken(principal, row))) {
|
|
250
|
+
throw new HttpError(403, "Token is not allowed.", "token_not_allowed");
|
|
251
|
+
}
|
|
252
|
+
await this.options.repository.touchAccessToken(row.tokenId);
|
|
253
|
+
return {
|
|
254
|
+
token,
|
|
255
|
+
clientId: row.clientId,
|
|
256
|
+
scopes: parseOAuthJsonArray(row.scopesJson),
|
|
257
|
+
expiresAt: row.accessExpiresAt,
|
|
258
|
+
resource: row.resource ? new URL(row.resource) : undefined,
|
|
259
|
+
subject,
|
|
260
|
+
extra: { ...subject },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async revokeToken(client: OAuthClientInformation, request: { token: string }): Promise<void> {
|
|
265
|
+
const accessRow = await this.options.repository.getTokenByAccessToken(request.token);
|
|
266
|
+
if (accessRow && accessRow.clientId === client.client_id) {
|
|
267
|
+
await this.options.repository.revokeToken(accessRow.tokenId);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const refreshRow = await this.options.repository.getTokenByRefreshToken(request.token);
|
|
272
|
+
if (refreshRow && refreshRow.clientId === client.client_id) await this.options.repository.revokeToken(refreshRow.tokenId);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private async validAuthorizationCode(client: OAuthClientInformation, authorizationCode: string) {
|
|
276
|
+
const row = await this.options.repository.getAuthorizationCode(authorizationCode);
|
|
277
|
+
if (!row || row.clientId !== client.client_id || row.usedAt || row.expiresAt < this.now()) {
|
|
278
|
+
throw invalidGrant("Invalid authorization code");
|
|
279
|
+
}
|
|
280
|
+
return row;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private async issueTokens(input: {
|
|
284
|
+
clientId: string;
|
|
285
|
+
subject: TSubject;
|
|
286
|
+
scopes: string[];
|
|
287
|
+
resource?: URL;
|
|
288
|
+
}): Promise<OAuthTokens> {
|
|
289
|
+
return (await this.issueTokenSet(input)).tokens;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async issueTokenSet(input: {
|
|
293
|
+
clientId: string;
|
|
294
|
+
subject: TSubject;
|
|
295
|
+
scopes: string[];
|
|
296
|
+
resource?: URL;
|
|
297
|
+
}) {
|
|
298
|
+
return issueMcpOAuthTokenSet(
|
|
299
|
+
this.options.repository,
|
|
300
|
+
{
|
|
301
|
+
clientId: input.clientId,
|
|
302
|
+
subject: input.subject,
|
|
303
|
+
scopes: input.scopes,
|
|
304
|
+
resource: input.resource,
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
accessTokenTtlSeconds: this.options.accessTokenTtlSeconds ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS,
|
|
308
|
+
refreshTokenTtlSeconds: this.options.refreshTokenTtlSeconds ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS,
|
|
309
|
+
createAccessToken: this.options.createAccessToken,
|
|
310
|
+
createRefreshToken: this.options.createRefreshToken,
|
|
311
|
+
now: this.options.now,
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private async cachedRefreshResponse(row: McpTokenRow): Promise<OAuthTokens | null> {
|
|
317
|
+
if (!row.rotatedResponseCiphertext || !row.rotatedResponseNonce || !row.refreshReuseExpiresAt || row.refreshReuseExpiresAt < this.now()) return null;
|
|
318
|
+
return decryptOAuthTokenResponse(row.rotatedResponseCiphertext, row.rotatedResponseNonce, this.options.tokenEncryptionSecret);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private defaultScopes(): string[] {
|
|
322
|
+
return this.options.defaultScopes?.length ? this.options.defaultScopes : [DEFAULT_SCOPE];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private loginRedirect(request: Request): string {
|
|
326
|
+
const loginPath = this.options.loginPath ?? "/login";
|
|
327
|
+
const url = new URL(loginPath, request.url);
|
|
328
|
+
url.searchParams.set("next", request.url);
|
|
329
|
+
return `${url.pathname}${url.search}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private now(): number {
|
|
333
|
+
return this.options.now?.() ?? Math.floor(Date.now() / 1000);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function invalidGrant(message: string): Error {
|
|
338
|
+
const error = new Error(message);
|
|
339
|
+
error.name = "InvalidGrantError";
|
|
340
|
+
return error;
|
|
341
|
+
}
|
package/src/hono.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AuthSystem, McpAuthService, McpAuthorizationParams } from "./auth.js";
|
|
2
|
+
import { errorResponse, HttpError } from "./http.js";
|
|
3
|
+
import type { OAuthClientInformation } from "./oauth.js";
|
|
4
|
+
|
|
5
|
+
export type HonoContextLike<TEnv = unknown> = {
|
|
6
|
+
req: {
|
|
7
|
+
raw: Request;
|
|
8
|
+
url: string;
|
|
9
|
+
};
|
|
10
|
+
env?: TEnv;
|
|
11
|
+
set?: (key: string, value: unknown) => void;
|
|
12
|
+
get?: (key: string) => unknown;
|
|
13
|
+
json: (value: unknown, status?: number) => Response;
|
|
14
|
+
redirect: (location: string, status?: number) => Response;
|
|
15
|
+
res?: Response;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type HonoNext = () => Promise<void>;
|
|
19
|
+
|
|
20
|
+
export function honoAuthMiddleware<TPrincipal, TSession>(
|
|
21
|
+
auth: AuthSystem<TPrincipal, TSession>,
|
|
22
|
+
options: { contextKey?: string; requirePrincipal?: boolean } = {},
|
|
23
|
+
): (context: HonoContextLike, next: HonoNext) => Promise<Response | void> {
|
|
24
|
+
return async (context, next) => {
|
|
25
|
+
try {
|
|
26
|
+
const authContext = await auth.contextFromRequest(context.req.raw);
|
|
27
|
+
if (options.requirePrincipal && !authContext.principal) {
|
|
28
|
+
throw new HttpError(401, "Authentication required.", "authentication_required");
|
|
29
|
+
}
|
|
30
|
+
context.set?.(options.contextKey ?? "auth", authContext);
|
|
31
|
+
await next();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return errorResponse(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function honoRequirePrincipal<TPrincipal, TSession>(
|
|
39
|
+
context: HonoContextLike,
|
|
40
|
+
auth: AuthSystem<TPrincipal, TSession>,
|
|
41
|
+
): Promise<TPrincipal> {
|
|
42
|
+
return auth.requirePrincipal(context.req.raw);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function honoMcpAuthorize<TPrincipal, TSubject extends Record<string, string>>(
|
|
46
|
+
context: HonoContextLike,
|
|
47
|
+
service: McpAuthService<TPrincipal, TSubject>,
|
|
48
|
+
client: OAuthClientInformation,
|
|
49
|
+
params: McpAuthorizationParams,
|
|
50
|
+
): Promise<Response> {
|
|
51
|
+
const result = await service.authorize(client, params, context.req.raw);
|
|
52
|
+
if (result.kind === "redirect") return context.redirect(result.location, result.status ?? 302);
|
|
53
|
+
return context.redirect(result.redirect, 302);
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./api.js";
|
|
2
2
|
export * from "./ai.js";
|
|
3
|
+
export * from "./auth.js";
|
|
3
4
|
export * from "./billing.js";
|
|
4
5
|
export * from "./cloudflare.js";
|
|
5
6
|
export * from "./crypto.js";
|
|
@@ -7,6 +8,7 @@ export * from "./d1.js";
|
|
|
7
8
|
export * from "./encoding.js";
|
|
8
9
|
export * from "./email.js";
|
|
9
10
|
export * from "./env.js";
|
|
11
|
+
export * from "./hono.js";
|
|
10
12
|
export * from "./http.js";
|
|
11
13
|
export * from "./ids.js";
|
|
12
14
|
export * from "./jobs.js";
|
|
@@ -17,6 +19,7 @@ export * from "./pg.js";
|
|
|
17
19
|
export * from "./pg-kysely.js";
|
|
18
20
|
export * from "./r2-json.js";
|
|
19
21
|
export * from "./rate-limit.js";
|
|
22
|
+
export * from "./react-router.js";
|
|
20
23
|
export * from "./seo.js";
|
|
21
24
|
export * from "./session.js";
|
|
22
25
|
export * from "./testing.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AuthSystem, McpAuthService, McpAuthorizationParams } from "./auth.js";
|
|
2
|
+
import { HttpError, jsonResponse } from "./http.js";
|
|
3
|
+
import type { OAuthClientInformation } from "./oauth.js";
|
|
4
|
+
|
|
5
|
+
export type ReactRouterRequestArgs = {
|
|
6
|
+
request: Request;
|
|
7
|
+
params?: Record<string, string | undefined>;
|
|
8
|
+
context?: unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function reactRouterAuthContext<TPrincipal, TSession>(
|
|
12
|
+
auth: AuthSystem<TPrincipal, TSession>,
|
|
13
|
+
args: ReactRouterRequestArgs,
|
|
14
|
+
) {
|
|
15
|
+
return auth.contextFromRequest(args.request);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function reactRouterRequirePrincipal<TPrincipal, TSession>(
|
|
19
|
+
auth: AuthSystem<TPrincipal, TSession>,
|
|
20
|
+
args: ReactRouterRequestArgs,
|
|
21
|
+
): Promise<TPrincipal> {
|
|
22
|
+
return auth.requirePrincipal(args.request);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function reactRouterJson(value: unknown, init: ResponseInit = {}): Response {
|
|
26
|
+
return jsonResponse(value, init);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function reactRouterRedirect(location: string, status = 302): Response {
|
|
30
|
+
return new Response(null, {
|
|
31
|
+
status,
|
|
32
|
+
headers: { location },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function reactRouterError(error: unknown): Response {
|
|
37
|
+
if (error instanceof Response) return error;
|
|
38
|
+
if (error instanceof HttpError) {
|
|
39
|
+
return jsonResponse({ error: { code: error.code, message: error.message } }, { status: error.status });
|
|
40
|
+
}
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
return jsonResponse({ error: { code: "internal_error", message } }, { status: 500 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function reactRouterMcpAuthorize<TPrincipal, TSubject extends Record<string, string>>(
|
|
46
|
+
args: ReactRouterRequestArgs,
|
|
47
|
+
service: McpAuthService<TPrincipal, TSubject>,
|
|
48
|
+
client: OAuthClientInformation,
|
|
49
|
+
params: McpAuthorizationParams,
|
|
50
|
+
): Promise<Response> {
|
|
51
|
+
const result = await service.authorize(client, params, args.request);
|
|
52
|
+
return reactRouterRedirect(result.kind === "redirect" ? result.location : result.redirect, result.kind === "redirect" ? (result.status ?? 302) : 302);
|
|
53
|
+
}
|