@boxxy/auth-oauth 0.2.6 → 0.2.7
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 +12 -13
- package/dist/auth/create-auth-admin.d.ts +25 -12
- package/dist/auth/create-security-event-log.d.ts +1 -1
- package/dist/auth/format-security-event.d.ts +10 -2
- package/dist/auth/open-auth-database.d.ts +2 -2
- package/dist/http/create-public-door.d.ts +5 -1
- package/dist/http/{answer-mcp-request.d.ts → types.d.ts} +0 -2
- package/dist/index.d.ts +2 -5
- package/dist/migrations/grant-rows-migration.d.ts +2 -0
- package/dist/migrations/run-auth-migrations.d.ts +2 -0
- package/dist/test-utils/authorize-test-client.d.ts +1 -13
- package/dist/test-utils/pre-package-schema.d.ts +1 -0
- package/dist/test-utils/seed-pre-package-auth.d.ts +21 -0
- package/package.json +7 -13
- package/src/auth/create-auth-admin.ts +109 -41
- package/src/auth/create-security-event-log.ts +1 -3
- package/src/auth/format-security-event.ts +12 -11
- package/src/auth/normalize-client-name.ts +0 -2
- package/src/auth/open-auth-database.ts +8 -13
- package/src/http/create-public-door.ts +113 -29
- package/src/http/is-loopback-host.ts +0 -2
- package/src/http/normalize-public-url.ts +0 -2
- package/src/http/types.ts +11 -0
- package/src/index.ts +9 -5
- package/src/migrations/grant-rows-migration.ts +108 -0
- package/src/migrations/run-auth-migrations.ts +53 -0
- package/src/test-utils/authorize-test-client.ts +2 -194
- package/src/test-utils/build-stub-clock.ts +0 -2
- package/src/test-utils/pre-package-schema.ts +36 -0
- package/src/test-utils/register-test-cleanup.ts +0 -2
- package/src/test-utils/seed-pre-package-auth.ts +162 -0
- package/src/test-utils/setup-temp-dir.ts +0 -2
- package/src/utils/types.ts +0 -2
- package/dist/auth/answer-authorize-request.d.ts +0 -2
- package/dist/auth/answer-consent-request.d.ts +0 -10
- package/dist/auth/answer-enroll-request.d.ts +0 -9
- package/dist/auth/answer-login-request.d.ts +0 -9
- package/dist/auth/approval-window-seconds.d.ts +0 -1
- package/dist/auth/build-consent-binding.d.ts +0 -1
- package/dist/auth/create-approval-state.d.ts +0 -41
- package/dist/auth/create-enrollment-state.d.ts +0 -6
- package/dist/auth/create-enrollment-token.d.ts +0 -6
- package/dist/auth/create-owner-plugin.d.ts +0 -38
- package/dist/auth/create-passkey.d.ts +0 -11
- package/dist/auth/derive-token-hash.d.ts +0 -1
- package/dist/auth/is-allowed-redirect-uri.d.ts +0 -1
- package/dist/auth/load-web-authn.d.ts +0 -6
- package/dist/auth/mcp-auth-database.d.ts +0 -5
- package/dist/auth/mcp-auth-schema.d.ts +0 -54
- package/dist/auth/open-mcp-auth.d.ts +0 -80
- package/dist/auth/passkey-script.d.ts +0 -4
- package/dist/auth/read-client-name.d.ts +0 -3
- package/dist/auth/read-client-views.d.ts +0 -9
- package/dist/auth/read-enrollment-token.d.ts +0 -3
- package/dist/auth/read-grant-views.d.ts +0 -12
- package/dist/auth/read-owner-session-id.d.ts +0 -2
- package/dist/auth/record-refused-exchange.d.ts +0 -10
- package/dist/auth/remove-client.d.ts +0 -3
- package/dist/auth/remove-owner-session.d.ts +0 -3
- package/dist/auth/render-consent-page.d.ts +0 -9
- package/dist/auth/render-enroll-page.d.ts +0 -10
- package/dist/auth/render-login-page.d.ts +0 -9
- package/dist/auth/revoke-grant.d.ts +0 -3
- package/dist/auth/to-html-text.d.ts +0 -1
- package/dist/auth/to-script-json.d.ts +0 -1
- package/dist/auth/types.d.ts +0 -79
- package/dist/auth/update-passkey-use.d.ts +0 -10
- package/dist/auth/verify-oauth-query.d.ts +0 -1
- package/dist/auth/verify-passkey-assertion.d.ts +0 -11
- package/dist/auth/verify-passkey-registration.d.ts +0 -14
- package/dist/auth/verify-session-passkey.d.ts +0 -8
- package/dist/auth/write-grant-use.d.ts +0 -3
- package/dist/http/build-page-response.d.ts +0 -5
- package/dist/http/create-mcp-http-app.d.ts +0 -92
- package/dist/http/pick-error-message.d.ts +0 -1
- package/dist/http/public-door-paths.d.ts +0 -17
- package/dist/http/read-token-access.d.ts +0 -8
- package/dist/http/verify-access-token.d.ts +0 -2
- package/dist/test-utils/build-mock-new-passkey.d.ts +0 -2
- package/dist/test-utils/build-passkey-seeder.d.ts +0 -5
- package/dist/test-utils/build-seeder.d.ts +0 -5
- package/dist/test-utils/build-stub-authenticator.d.ts +0 -48
- package/dist/test-utils/load-web-authn-early.d.ts +0 -1
- package/dist/test-utils/seed-passkey.d.ts +0 -4
- package/dist/test-utils/start-stub-telemetry-collector.d.ts +0 -5
- package/dist/test-utils/types.d.ts +0 -7
- package/dist/utils/is-record.d.ts +0 -1
- package/dist/utils/parse-json-strings.d.ts +0 -1
- package/src/auth/answer-authorize-request.ts +0 -137
- package/src/auth/answer-consent-request.ts +0 -189
- package/src/auth/answer-enroll-request.ts +0 -190
- package/src/auth/answer-login-request.ts +0 -256
- package/src/auth/approval-window-seconds.ts +0 -5
- package/src/auth/build-consent-binding.ts +0 -13
- package/src/auth/create-approval-state.ts +0 -221
- package/src/auth/create-enrollment-state.ts +0 -56
- package/src/auth/create-enrollment-token.ts +0 -28
- package/src/auth/create-owner-plugin.ts +0 -113
- package/src/auth/create-passkey.ts +0 -48
- package/src/auth/derive-token-hash.ts +0 -7
- package/src/auth/is-allowed-redirect-uri.ts +0 -19
- package/src/auth/load-web-authn.ts +0 -19
- package/src/auth/mcp-auth-database.ts +0 -10
- package/src/auth/mcp-auth-schema.ts +0 -56
- package/src/auth/open-mcp-auth.ts +0 -138
- package/src/auth/passkey-script.ts +0 -50
- package/src/auth/read-client-name.ts +0 -17
- package/src/auth/read-client-views.ts +0 -27
- package/src/auth/read-enrollment-token.ts +0 -20
- package/src/auth/read-grant-views.ts +0 -53
- package/src/auth/read-owner-session-id.ts +0 -16
- package/src/auth/record-refused-exchange.ts +0 -74
- package/src/auth/remove-client.ts +0 -33
- package/src/auth/remove-owner-session.ts +0 -11
- package/src/auth/render-consent-page.ts +0 -61
- package/src/auth/render-enroll-page.ts +0 -59
- package/src/auth/render-login-page.ts +0 -58
- package/src/auth/revoke-grant.ts +0 -25
- package/src/auth/to-html-text.ts +0 -10
- package/src/auth/to-script-json.ts +0 -8
- package/src/auth/types.ts +0 -115
- package/src/auth/update-passkey-use.ts +0 -31
- package/src/auth/verify-oauth-query.ts +0 -41
- package/src/auth/verify-passkey-assertion.ts +0 -124
- package/src/auth/verify-passkey-registration.ts +0 -118
- package/src/auth/verify-session-passkey.ts +0 -47
- package/src/auth/write-grant-use.ts +0 -18
- package/src/http/answer-mcp-request.ts +0 -77
- package/src/http/build-page-response.ts +0 -26
- package/src/http/create-mcp-http-app.ts +0 -340
- package/src/http/pick-error-message.ts +0 -21
- package/src/http/public-door-paths.ts +0 -21
- package/src/http/read-token-access.ts +0 -44
- package/src/http/verify-access-token.ts +0 -46
- package/src/test-utils/build-mock-new-passkey.ts +0 -15
- package/src/test-utils/build-passkey-seeder.ts +0 -11
- package/src/test-utils/build-seeder.ts +0 -10
- package/src/test-utils/build-stub-authenticator.ts +0 -244
- package/src/test-utils/load-web-authn-early.ts +0 -8
- package/src/test-utils/seed-passkey.ts +0 -32
- package/src/test-utils/start-stub-telemetry-collector.ts +0 -27
- package/src/test-utils/types.ts +0 -11
- package/src/utils/is-record.ts +0 -5
- package/src/utils/parse-json-strings.ts +0 -15
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { PRE_PACKAGE_SCHEMA } from './pre-package-schema.ts';
|
|
4
|
+
|
|
5
|
+
export interface PrePackageGrant {
|
|
6
|
+
readonly clientName: string;
|
|
7
|
+
readonly scopes: readonly string[];
|
|
8
|
+
|
|
9
|
+
// how many times the client refreshed; each refresh rotated out the token pair before it
|
|
10
|
+
readonly refreshes: number;
|
|
11
|
+
|
|
12
|
+
// what the old `boxxy_grant_use` table held for the grant
|
|
13
|
+
readonly lastUsedAt: string | null;
|
|
14
|
+
|
|
15
|
+
// true: every token of the grant expired an hour ago
|
|
16
|
+
readonly isExpired?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PrePackageAuth {
|
|
20
|
+
readonly origin: string;
|
|
21
|
+
readonly grants: readonly PrePackageGrant[];
|
|
22
|
+
readonly passkeyID: string;
|
|
23
|
+
readonly now: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// One per seeded grant, in the order of the input: its IDs, and its newest token pair.
|
|
27
|
+
export interface SeededPrePackageGrant {
|
|
28
|
+
readonly grantID: string;
|
|
29
|
+
readonly clientID: string;
|
|
30
|
+
readonly accessToken: string;
|
|
31
|
+
readonly refreshToken: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const OWNER_ID = 'owner-user-row';
|
|
35
|
+
const HOUR_MS = 60 * 60_000;
|
|
36
|
+
|
|
37
|
+
// Writes the auth database the way boxxy's own OAuth server left it before the package: one owner
|
|
38
|
+
// `user` row that every token, session and consent points at, and the grant-use table. No old
|
|
39
|
+
// module is left to seed through, so the rows go in directly, as that server wrote them.
|
|
40
|
+
export function seedPrePackageAuth(
|
|
41
|
+
sqlite: Database,
|
|
42
|
+
input: PrePackageAuth,
|
|
43
|
+
): SeededPrePackageGrant[] {
|
|
44
|
+
const at = (offsetMs: number) => new Date(input.now + offsetMs).toISOString();
|
|
45
|
+
|
|
46
|
+
return sqlite.transaction(() => {
|
|
47
|
+
for (const statement of PRE_PACKAGE_SCHEMA) {
|
|
48
|
+
sqlite.run(statement);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sqlite.run(
|
|
52
|
+
`INSERT INTO "user" (id, name, email, emailVerified, createdAt, updatedAt) VALUES (?, 'owner', 'owner@boxxy.invalid', 1, ?, ?)`,
|
|
53
|
+
[OWNER_ID, at(-HOUR_MS), at(-HOUR_MS)],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
sqlite.run(
|
|
57
|
+
`INSERT INTO session (id, expiresAt, token, createdAt, updatedAt, userId) VALUES ('owner-session', ?, 'owner-session-token', ?, ?, ?)`,
|
|
58
|
+
[at(HOUR_MS), at(-HOUR_MS), at(-HOUR_MS), OWNER_ID],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
sqlite.run(
|
|
62
|
+
`INSERT INTO oauthResource (id, identifier, name, customClaims, dpopBoundAccessTokensRequired, disabled, createdAt, updatedAt, policyVersion, metadata) VALUES ('resource-1', ?, ?, 'null', 0, 0, ?, ?, 1, 'null')`,
|
|
63
|
+
[`${input.origin}/mcp`, `${input.origin}/mcp`, at(-HOUR_MS), at(-HOUR_MS)],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
sqlite.run(
|
|
67
|
+
`INSERT INTO boxxy_passkey (id, public_key, counter, transports, created_at, last_used_at) VALUES (?, 'pQECAyYgASFYIA', 1, '["internal"]', ?, ?)`,
|
|
68
|
+
[input.passkeyID, at(-HOUR_MS), at(-HOUR_MS)],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return input.grants.map((grant, index) =>
|
|
72
|
+
seedGrant(sqlite, grant, { index, at, resource: JSON.stringify([`${input.origin}/mcp`]) }),
|
|
73
|
+
);
|
|
74
|
+
})();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface GrantContext {
|
|
78
|
+
readonly index: number;
|
|
79
|
+
readonly at: (offsetMs: number) => string;
|
|
80
|
+
readonly resource: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function seedGrant(
|
|
84
|
+
sqlite: Database,
|
|
85
|
+
grant: PrePackageGrant,
|
|
86
|
+
ctx: GrantContext,
|
|
87
|
+
): SeededPrePackageGrant {
|
|
88
|
+
const clientID = `client-${ctx.index}`;
|
|
89
|
+
const grantID = `code-${ctx.index}`;
|
|
90
|
+
const scopes = JSON.stringify([...grant.scopes, 'offline_access']);
|
|
91
|
+
const createdAt = ctx.at(-2 * HOUR_MS);
|
|
92
|
+
const accessExpiresAt = grant.isExpired === true ? ctx.at(-HOUR_MS) : ctx.at(HOUR_MS);
|
|
93
|
+
const refreshExpiresAt = grant.isExpired === true ? ctx.at(-HOUR_MS) : ctx.at(30 * 24 * HOUR_MS);
|
|
94
|
+
|
|
95
|
+
sqlite.run(
|
|
96
|
+
`INSERT INTO oauthClient (id, clientId, disabled, scopes, clientCredentialsScopes, createdAt, updatedAt, name, redirectUris, tokenEndpointAuthMethod, applicationType, grantTypes, responseTypes, requirePKCE, dpopBoundAccessTokens) VALUES (?, ?, 0, '["read","write","offline_access"]', '[]', ?, ?, ?, '["https://client.example/callback"]', 'none', 'web', '["authorization_code","refresh_token"]', '["code"]', 1, 0)`,
|
|
97
|
+
[`row-${clientID}`, clientID, createdAt, createdAt, grant.clientName],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
sqlite.run(
|
|
101
|
+
`INSERT INTO oauthConsent (id, clientId, userId, resources, requestedUserInfoClaims, scopes, createdAt, updatedAt) VALUES (?, ?, ?, ?, '[]', ?, ?, ?)`,
|
|
102
|
+
[`consent-${clientID}`, clientID, OWNER_ID, ctx.resource, scopes, createdAt, createdAt],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
for (let pair = 0; pair <= grant.refreshes; pair++) {
|
|
106
|
+
const refreshID = `refresh-${grantID}-${pair}`;
|
|
107
|
+
const rotatedAt = pair < grant.refreshes ? createdAt : null;
|
|
108
|
+
|
|
109
|
+
sqlite.run(
|
|
110
|
+
`INSERT INTO oauthRefreshToken (id, token, clientId, userId, authorizationCodeId, resources, expiresAt, createdAt, revoked, rotatedAt, authTime, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
111
|
+
[
|
|
112
|
+
refreshID,
|
|
113
|
+
toTokenHash(`refresh-token-${grantID}-${pair}`),
|
|
114
|
+
clientID,
|
|
115
|
+
OWNER_ID,
|
|
116
|
+
grantID,
|
|
117
|
+
ctx.resource,
|
|
118
|
+
refreshExpiresAt,
|
|
119
|
+
createdAt,
|
|
120
|
+
rotatedAt,
|
|
121
|
+
rotatedAt,
|
|
122
|
+
createdAt,
|
|
123
|
+
scopes,
|
|
124
|
+
],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
sqlite.run(
|
|
128
|
+
`INSERT INTO oauthAccessToken (id, token, clientId, userId, authorizationCodeId, resources, refreshId, expiresAt, createdAt, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
129
|
+
[
|
|
130
|
+
`access-${grantID}-${pair}`,
|
|
131
|
+
toTokenHash(`access-token-${grantID}-${pair}`),
|
|
132
|
+
clientID,
|
|
133
|
+
OWNER_ID,
|
|
134
|
+
grantID,
|
|
135
|
+
ctx.resource,
|
|
136
|
+
refreshID,
|
|
137
|
+
accessExpiresAt,
|
|
138
|
+
createdAt,
|
|
139
|
+
scopes,
|
|
140
|
+
],
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (grant.lastUsedAt !== null) {
|
|
145
|
+
sqlite.run(`INSERT INTO boxxy_grant_use (grant_id, last_used_at) VALUES (?, ?)`, [
|
|
146
|
+
grantID,
|
|
147
|
+
grant.lastUsedAt,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
grantID,
|
|
153
|
+
clientID,
|
|
154
|
+
accessToken: `access-token-${grantID}-${grant.refreshes}`,
|
|
155
|
+
refreshToken: `refresh-token-${grantID}-${grant.refreshes}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// better-auth stores each token as its SHA-256 digest, in base64url.
|
|
160
|
+
function toTokenHash(token: string): string {
|
|
161
|
+
return createHash('sha256').update(token).digest('base64url');
|
|
162
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { HTTPServerContext, Requester } from './types.ts';
|
|
2
|
-
interface ConsentRequest {
|
|
3
|
-
readonly method: 'GET' | 'POST';
|
|
4
|
-
readonly url: URL;
|
|
5
|
-
readonly cookie: string | null;
|
|
6
|
-
readonly body: string;
|
|
7
|
-
readonly requester: Requester;
|
|
8
|
-
}
|
|
9
|
-
export declare function answerConsentRequest(ctx: HTTPServerContext, request: ConsentRequest): Promise<Response>;
|
|
10
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { HTTPServerContext, Requester } from './types.ts';
|
|
2
|
-
interface EnrollRequest {
|
|
3
|
-
readonly method: 'GET' | 'POST';
|
|
4
|
-
readonly url: URL;
|
|
5
|
-
readonly body: string;
|
|
6
|
-
readonly requester: Requester;
|
|
7
|
-
}
|
|
8
|
-
export declare function answerEnrollRequest(ctx: HTTPServerContext, request: EnrollRequest): Promise<Response>;
|
|
9
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { HTTPServerContext, Requester } from './types.ts';
|
|
2
|
-
interface LoginRequest {
|
|
3
|
-
readonly method: 'GET' | 'POST';
|
|
4
|
-
readonly url: URL;
|
|
5
|
-
readonly body: string;
|
|
6
|
-
readonly requester: Requester;
|
|
7
|
-
}
|
|
8
|
-
export declare function answerLoginRequest(ctx: HTTPServerContext, request: LoginRequest): Promise<Response>;
|
|
9
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const APPROVAL_WINDOW_SECONDS = 600;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function buildConsentBinding(oauthQuery: string): string;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
interface ApprovalRequest {
|
|
2
|
-
readonly key: string;
|
|
3
|
-
readonly clientID: string;
|
|
4
|
-
readonly clientName: string;
|
|
5
|
-
readonly redirectURI: string;
|
|
6
|
-
}
|
|
7
|
-
interface PendingApproval extends ApprovalRequest {
|
|
8
|
-
readonly correlationID: string;
|
|
9
|
-
failures: number;
|
|
10
|
-
readonly expiresAt: number;
|
|
11
|
-
challenge: {
|
|
12
|
-
readonly value: string;
|
|
13
|
-
readonly expiresAt: number;
|
|
14
|
-
} | null;
|
|
15
|
-
}
|
|
16
|
-
export type TakenChallenge = {
|
|
17
|
-
readonly kind: 'taken';
|
|
18
|
-
readonly challenge: string;
|
|
19
|
-
} | {
|
|
20
|
-
readonly kind: 'expired';
|
|
21
|
-
} | {
|
|
22
|
-
readonly kind: 'missing';
|
|
23
|
-
};
|
|
24
|
-
interface ApprovedRequest {
|
|
25
|
-
readonly binding: string;
|
|
26
|
-
readonly correlationID: string;
|
|
27
|
-
readonly expiresAt: number;
|
|
28
|
-
}
|
|
29
|
-
export interface ApprovalState {
|
|
30
|
-
readonly createPending: (request: ApprovalRequest) => PendingApproval | null;
|
|
31
|
-
readonly findPending: (key: string) => PendingApproval | null;
|
|
32
|
-
readonly mintChallenge: (key: string) => string | null;
|
|
33
|
-
readonly claimChallenge: (key: string) => TakenChallenge;
|
|
34
|
-
readonly recordFailure: (key: string) => 'retry' | 'locked';
|
|
35
|
-
readonly removePending: (key: string) => void;
|
|
36
|
-
readonly recordApproved: (sessionID: string, approved: Omit<ApprovedRequest, 'expiresAt'>) => void;
|
|
37
|
-
readonly findApproved: (sessionID: string) => ApprovedRequest | null;
|
|
38
|
-
readonly removeApproved: (sessionID: string) => void;
|
|
39
|
-
}
|
|
40
|
-
export declare function createApprovalState(pendingMs: number, now: () => number): ApprovalState;
|
|
41
|
-
export {};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { TakenChallenge } from './create-approval-state.ts';
|
|
2
|
-
export interface EnrollmentState {
|
|
3
|
-
readonly mintChallenge: (tokenHash: string) => string;
|
|
4
|
-
readonly claimChallenge: (tokenHash: string) => TakenChallenge;
|
|
5
|
-
}
|
|
6
|
-
export declare function createEnrollmentState(now: () => number): EnrollmentState;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare function createOwnerPlugin(): {
|
|
3
|
-
id: "boxxy-owner";
|
|
4
|
-
endpoints: {
|
|
5
|
-
signInOwner: import("better-auth").StrictEndpoint<"/boxxy/sign-in-owner", {
|
|
6
|
-
method: "POST";
|
|
7
|
-
body: z.ZodObject<{
|
|
8
|
-
oauth_query: z.ZodOptional<z.ZodString>;
|
|
9
|
-
}, z.core.$strip>;
|
|
10
|
-
metadata: {
|
|
11
|
-
SERVER_ONLY: true;
|
|
12
|
-
};
|
|
13
|
-
}, {
|
|
14
|
-
signedIn: boolean;
|
|
15
|
-
}>;
|
|
16
|
-
verifyMCPAccessToken: import("better-auth").StrictEndpoint<"/boxxy/verify-access-token", {
|
|
17
|
-
method: "POST";
|
|
18
|
-
body: z.ZodObject<{
|
|
19
|
-
token: z.ZodString;
|
|
20
|
-
}, z.core.$strip>;
|
|
21
|
-
metadata: {
|
|
22
|
-
SERVER_ONLY: true;
|
|
23
|
-
};
|
|
24
|
-
}, import("@better-auth/oauth-provider").ActiveAccessTokenPayload>;
|
|
25
|
-
createFixedClient: import("better-auth").StrictEndpoint<"/boxxy/create-fixed-client", {
|
|
26
|
-
method: "POST";
|
|
27
|
-
body: z.ZodObject<{
|
|
28
|
-
name: z.ZodString;
|
|
29
|
-
redirectURIs: z.ZodArray<z.ZodString>;
|
|
30
|
-
}, z.core.$strip>;
|
|
31
|
-
metadata: {
|
|
32
|
-
SERVER_ONLY: true;
|
|
33
|
-
};
|
|
34
|
-
}, {
|
|
35
|
-
clientID: string;
|
|
36
|
-
}>;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { MCPAuthSchema } from './mcp-auth-schema.ts';
|
|
3
|
-
export interface NewPasskey {
|
|
4
|
-
readonly tokenHash: string;
|
|
5
|
-
readonly id: string;
|
|
6
|
-
readonly publicKey: string;
|
|
7
|
-
readonly counter: number;
|
|
8
|
-
readonly transports: readonly string[];
|
|
9
|
-
readonly now: number;
|
|
10
|
-
}
|
|
11
|
-
export declare function createPasskey(db: Kysely<MCPAuthSchema>, passkey: NewPasskey): Promise<boolean>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function deriveTokenHash(token: string): string;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isAllowedRedirectURI(raw: string): boolean;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { verifyAuthenticationResponse, verifyRegistrationResponse } from '@simplewebauthn/server';
|
|
2
|
-
export interface WebAuthn {
|
|
3
|
-
readonly verifyAuthenticationResponse: typeof verifyAuthenticationResponse;
|
|
4
|
-
readonly verifyRegistrationResponse: typeof verifyRegistrationResponse;
|
|
5
|
-
}
|
|
6
|
-
export declare function loadWebAuthn(): Promise<WebAuthn>;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
export interface MCPAuthSchema {
|
|
2
|
-
readonly oauthClient: {
|
|
3
|
-
readonly id: string;
|
|
4
|
-
readonly clientId: string;
|
|
5
|
-
readonly name: string | null;
|
|
6
|
-
readonly redirectUris: string;
|
|
7
|
-
readonly createdAt: string | null;
|
|
8
|
-
};
|
|
9
|
-
readonly oauthAccessToken: {
|
|
10
|
-
readonly id: string;
|
|
11
|
-
readonly token: string;
|
|
12
|
-
readonly clientId: string;
|
|
13
|
-
readonly sessionId: string | null;
|
|
14
|
-
readonly authorizationCodeId: string | null;
|
|
15
|
-
};
|
|
16
|
-
readonly oauthRefreshToken: {
|
|
17
|
-
readonly id: string;
|
|
18
|
-
readonly clientId: string;
|
|
19
|
-
readonly sessionId: string | null;
|
|
20
|
-
readonly authorizationCodeId: string | null;
|
|
21
|
-
readonly scopes: string;
|
|
22
|
-
readonly createdAt: string;
|
|
23
|
-
readonly expiresAt: string;
|
|
24
|
-
readonly revoked: string | null;
|
|
25
|
-
};
|
|
26
|
-
readonly oauthConsent: {
|
|
27
|
-
readonly id: string;
|
|
28
|
-
readonly clientId: string;
|
|
29
|
-
};
|
|
30
|
-
readonly oauthResource: {
|
|
31
|
-
readonly identifier: string;
|
|
32
|
-
};
|
|
33
|
-
readonly session: {
|
|
34
|
-
readonly id: string;
|
|
35
|
-
readonly expiresAt: string;
|
|
36
|
-
};
|
|
37
|
-
readonly boxxy_grant_use: {
|
|
38
|
-
readonly grant_id: string;
|
|
39
|
-
readonly last_used_at: string;
|
|
40
|
-
};
|
|
41
|
-
readonly boxxy_passkey: {
|
|
42
|
-
readonly id: string;
|
|
43
|
-
readonly public_key: string;
|
|
44
|
-
readonly counter: number;
|
|
45
|
-
readonly transports: string;
|
|
46
|
-
readonly created_at: string;
|
|
47
|
-
readonly last_used_at: string | null;
|
|
48
|
-
};
|
|
49
|
-
readonly boxxy_enrollment_token: {
|
|
50
|
-
readonly token_hash: string;
|
|
51
|
-
readonly created_at: string;
|
|
52
|
-
readonly expires_at: string;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { BetterAuthPlugin } from 'better-auth';
|
|
2
|
-
import { Kysely } from 'kysely';
|
|
3
|
-
import type { MCPAuthDatabase } from './mcp-auth-database.ts';
|
|
4
|
-
import type { MCPAuthSchema } from './mcp-auth-schema.ts';
|
|
5
|
-
interface MCPAuthOptions {
|
|
6
|
-
readonly dbPath: string;
|
|
7
|
-
readonly origin: string | null;
|
|
8
|
-
readonly refreshReuseSeconds?: number;
|
|
9
|
-
readonly openDatabase: (path: string) => MCPAuthDatabase;
|
|
10
|
-
}
|
|
11
|
-
export declare function openMCPAuth(options: MCPAuthOptions): Promise<{
|
|
12
|
-
auth: import("better-auth").Auth<{
|
|
13
|
-
baseURL: string;
|
|
14
|
-
basePath: string;
|
|
15
|
-
secret: string;
|
|
16
|
-
database: {
|
|
17
|
-
db: Kysely<MCPAuthSchema>;
|
|
18
|
-
type: "sqlite";
|
|
19
|
-
};
|
|
20
|
-
telemetry: {
|
|
21
|
-
enabled: false;
|
|
22
|
-
};
|
|
23
|
-
logger: {
|
|
24
|
-
level: "error";
|
|
25
|
-
};
|
|
26
|
-
rateLimit: {
|
|
27
|
-
enabled: false;
|
|
28
|
-
};
|
|
29
|
-
advanced: {
|
|
30
|
-
database: {
|
|
31
|
-
validateSchema: false;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
trustedOrigins: string[];
|
|
35
|
-
session: {
|
|
36
|
-
expiresIn: number;
|
|
37
|
-
disableSessionRefresh: true;
|
|
38
|
-
};
|
|
39
|
-
plugins: [BetterAuthPlugin, {
|
|
40
|
-
id: "boxxy-owner";
|
|
41
|
-
endpoints: {
|
|
42
|
-
signInOwner: import("better-auth").StrictEndpoint<"/boxxy/sign-in-owner", {
|
|
43
|
-
method: "POST";
|
|
44
|
-
body: import("zod").ZodObject<{
|
|
45
|
-
oauth_query: import("zod").ZodOptional<import("zod").ZodString>;
|
|
46
|
-
}, import("zod/v4/core").$strip>;
|
|
47
|
-
metadata: {
|
|
48
|
-
SERVER_ONLY: true;
|
|
49
|
-
};
|
|
50
|
-
}, {
|
|
51
|
-
signedIn: boolean;
|
|
52
|
-
}>;
|
|
53
|
-
verifyMCPAccessToken: import("better-auth").StrictEndpoint<"/boxxy/verify-access-token", {
|
|
54
|
-
method: "POST";
|
|
55
|
-
body: import("zod").ZodObject<{
|
|
56
|
-
token: import("zod").ZodString;
|
|
57
|
-
}, import("zod/v4/core").$strip>;
|
|
58
|
-
metadata: {
|
|
59
|
-
SERVER_ONLY: true;
|
|
60
|
-
};
|
|
61
|
-
}, import("@better-auth/oauth-provider").ActiveAccessTokenPayload>;
|
|
62
|
-
createFixedClient: import("better-auth").StrictEndpoint<"/boxxy/create-fixed-client", {
|
|
63
|
-
method: "POST";
|
|
64
|
-
body: import("zod").ZodObject<{
|
|
65
|
-
name: import("zod").ZodString;
|
|
66
|
-
redirectURIs: import("zod").ZodArray<import("zod").ZodString>;
|
|
67
|
-
}, import("zod/v4/core").$strip>;
|
|
68
|
-
metadata: {
|
|
69
|
-
SERVER_ONLY: true;
|
|
70
|
-
};
|
|
71
|
-
}, {
|
|
72
|
-
clientID: string;
|
|
73
|
-
}>;
|
|
74
|
-
};
|
|
75
|
-
}];
|
|
76
|
-
}>;
|
|
77
|
-
db: Kysely<MCPAuthSchema>;
|
|
78
|
-
close(): Promise<void>;
|
|
79
|
-
}>;
|
|
80
|
-
export {};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export declare const PASSKEY_SCRIPT: {
|
|
2
|
-
readonly source: "(() => {\n const data = document.getElementById('boxxy-passkey');\n const form = document.getElementById('boxxy-passkey-form');\n const button = document.getElementById('boxxy-passkey-start');\n const status = document.getElementById('boxxy-passkey-status');\n if (data === null || form === null || button === null || status === null) return;\n const spec = JSON.parse(data.textContent);\n const toBytes = (value) => {\n const base64 = value.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));\n };\n const toText = (buffer) => {\n let text = '';\n for (const byte of new Uint8Array(buffer)) text += String.fromCharCode(byte);\n return btoa(text).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n };\n const toDescriptors = (list) => (list || []).map((item) => ({ ...item, id: toBytes(item.id) }));\n button.addEventListener('click', async () => {\n button.disabled = true;\n status.textContent = '';\n try {\n const options = spec.options;\n const credential = spec.mode === 'create'\n ? await navigator.credentials.create({ publicKey: { ...options, challenge: toBytes(options.challenge), user: { ...options.user, id: toBytes(options.user.id) }, excludeCredentials: toDescriptors(options.excludeCredentials) } })\n : await navigator.credentials.get({ publicKey: { ...options, challenge: toBytes(options.challenge), allowCredentials: toDescriptors(options.allowCredentials) } });\n const reply = credential.response;\n const response = spec.mode === 'create'\n ? { clientDataJSON: toText(reply.clientDataJSON), attestationObject: toText(reply.attestationObject), transports: typeof reply.getTransports === 'function' ? reply.getTransports() : [] }\n : { clientDataJSON: toText(reply.clientDataJSON), authenticatorData: toText(reply.authenticatorData), signature: toText(reply.signature), userHandle: reply.userHandle ? toText(reply.userHandle) : undefined };\n form.elements.namedItem('credential').value = JSON.stringify({ id: credential.id, rawId: toText(credential.rawId), type: credential.type, response, clientExtensionResults: credential.getClientExtensionResults(), authenticatorAttachment: credential.authenticatorAttachment || undefined });\n form.submit();\n } catch (error) {\n status.textContent = 'The passkey prompt ended without a passkey (' + (error && error.name ? error.name : 'Error') + '). Try again.';\n button.disabled = false;\n }\n });\n})();";
|
|
3
|
-
readonly policySource: `'sha256-${string}'`;
|
|
4
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { MCPAuthSchema } from './mcp-auth-schema.ts';
|
|
3
|
-
export interface ClientView {
|
|
4
|
-
readonly clientID: string;
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly redirectURIs: readonly string[];
|
|
7
|
-
readonly createdAt: string | null;
|
|
8
|
-
}
|
|
9
|
-
export declare function readClientViews(db: Kysely<MCPAuthSchema>): Promise<readonly ClientView[]>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { GrantScope } from '@boxxy/contract';
|
|
2
|
-
import type { Kysely } from 'kysely';
|
|
3
|
-
import type { MCPAuthSchema } from './mcp-auth-schema.ts';
|
|
4
|
-
export interface GrantView {
|
|
5
|
-
readonly grantID: string;
|
|
6
|
-
readonly clientID: string;
|
|
7
|
-
readonly clientName: string;
|
|
8
|
-
readonly scopes: readonly GrantScope[];
|
|
9
|
-
readonly createdAt: string;
|
|
10
|
-
readonly lastUsedAt: string | null;
|
|
11
|
-
}
|
|
12
|
-
export declare function readGrantViews(db: Kysely<MCPAuthSchema>): Promise<readonly GrantView[]>;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { HTTPServerContext, Requester } from './types.ts';
|
|
2
|
-
interface RefusedExchange {
|
|
3
|
-
readonly contentType: string | null;
|
|
4
|
-
readonly body: string;
|
|
5
|
-
readonly response: Response;
|
|
6
|
-
readonly path: string;
|
|
7
|
-
readonly requester: Requester;
|
|
8
|
-
}
|
|
9
|
-
export declare function recordRefusedExchange(ctx: Pick<HTTPServerContext, 'store' | 'security'>, exchange: RefusedExchange): Promise<void>;
|
|
10
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { GrantScope } from '@boxxy/contract';
|
|
2
|
-
interface ConsentPageView {
|
|
3
|
-
readonly oauthQuery: string;
|
|
4
|
-
readonly clientName: string;
|
|
5
|
-
readonly redirectURI: string;
|
|
6
|
-
readonly scopes: readonly GrantScope[];
|
|
7
|
-
}
|
|
8
|
-
export declare function renderConsentPage(view: ConsentPageView): string;
|
|
9
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
interface EnrollPageView {
|
|
2
|
-
readonly ceremony: {
|
|
3
|
-
readonly token: string;
|
|
4
|
-
readonly options: unknown;
|
|
5
|
-
} | null;
|
|
6
|
-
readonly error: string | null;
|
|
7
|
-
readonly prefill?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function renderEnrollPage(view: EnrollPageView): string;
|
|
10
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
interface LoginPageView {
|
|
2
|
-
readonly oauthQuery: string;
|
|
3
|
-
readonly clientName: string;
|
|
4
|
-
readonly redirectURI: string;
|
|
5
|
-
readonly options: unknown;
|
|
6
|
-
readonly error: string | null;
|
|
7
|
-
}
|
|
8
|
-
export declare function renderLoginPage(view: LoginPageView): string;
|
|
9
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function toHTMLText(text: string): string;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function toScriptJSON(value: unknown): string;
|