@boxxy/auth-oauth 0.2.7 → 0.2.8

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.
@@ -19,9 +19,6 @@ export type RemoveClientAnswer = {
19
19
  readonly kind: 'removed';
20
20
  } | {
21
21
  readonly kind: 'not-found';
22
- } | {
23
- readonly kind: 'ambiguous';
24
- readonly name: string;
25
22
  };
26
23
  export interface EnrollmentLink {
27
24
  readonly token: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxxy/auth-oauth",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "boxxy's public door: an OAuth server for the owner's MCP endpoint, with passkey sign-in",
5
5
  "homepage": "https://github.com/zgeoff/boxxy/tree/main/adapters/auth-oauth#readme",
6
6
  "bugs": "https://github.com/zgeoff/boxxy/issues",
@@ -31,9 +31,9 @@
31
31
  "typecheck": "tsrs --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@boxxy/contract": "0.2.7",
34
+ "@boxxy/contract": "0.2.8",
35
35
  "@zgeoff/kysely-bun-sqlite": "0.3.2",
36
- "@zgeoff/mcp-auth": "0.1.0",
36
+ "@zgeoff/mcp-auth": "0.2.1",
37
37
  "@zgeoff/std": "0.4.3",
38
38
  "elysia": "2.0.0-rc.3",
39
39
  "exact-mirror": "1.2.7",
@@ -1,7 +1,7 @@
1
1
  import type { GrantScope } from '@boxxy/contract';
2
2
  import { grantScopes } from '@boxxy/contract';
3
3
  import { MCPAuthError } from '@zgeoff/mcp-auth';
4
- import type { ClientView, GrantID, GrantView, MCPAuth } from '@zgeoff/mcp-auth';
4
+ import type { ClientView, GrantID, GrantView, MCPAuth, OAuthClientID } from '@zgeoff/mcp-auth';
5
5
  import { toTagged } from '@zgeoff/std';
6
6
 
7
7
  export interface AuthAdminInput {
@@ -16,12 +16,7 @@ export type AddClientAnswer =
16
16
  | { readonly kind: 'refused'; readonly message: string }
17
17
  | { readonly kind: 'conflict'; readonly name: string };
18
18
 
19
- export type RemoveClientAnswer =
20
- | { readonly kind: 'removed' }
21
- | { readonly kind: 'not-found' }
22
-
23
- // another client has the same name, and the OAuth server removes a client by its name
24
- | { readonly kind: 'ambiguous'; readonly name: string };
19
+ export type RemoveClientAnswer = { readonly kind: 'removed' } | { readonly kind: 'not-found' };
25
20
 
26
21
  export interface EnrollmentLink {
27
22
  readonly token: string;
@@ -72,17 +67,13 @@ export function createAuthAdmin(input: AuthAdminInput): AuthAdmin {
72
67
  },
73
68
  listClients: () => auth.clients.list(),
74
69
  removeClient: async (clientID) => {
75
- const clients = await auth.clients.list();
76
-
77
- const answer = pickClientToRemove(clients, clientID);
78
-
79
- if (answer.kind === 'remove') {
80
- await auth.clients.remove(answer.name);
70
+ try {
71
+ await auth.clients.remove(toTagged<OAuthClientID>(clientID));
81
72
 
82
73
  return { kind: 'removed' };
74
+ } catch (error) {
75
+ return pickRemoveRefusal(error);
83
76
  }
84
-
85
- return answer;
86
77
  },
87
78
  listGrants: async () => {
88
79
  const grants = await auth.grants.list();
@@ -114,24 +105,14 @@ function pickAddRefusal(error: unknown, name: string): AddClientAnswer {
114
105
  throw error;
115
106
  }
116
107
 
117
- type ClientToRemove =
118
- | { readonly kind: 'remove'; readonly name: string }
119
- | Exclude<RemoveClientAnswer, { readonly kind: 'removed' }>;
120
-
121
- function pickClientToRemove(clients: readonly ClientView[], clientID: string): ClientToRemove {
122
- const client = clients.find((view) => view.clientID === clientID);
123
-
124
- if (client === undefined) {
108
+ // The package refuses an unknown client ID with a coded error; any other error is not the owner's
109
+ // input.
110
+ function pickRemoveRefusal(error: unknown): RemoveClientAnswer {
111
+ if (error instanceof MCPAuthError && error.code === 'not-found') {
125
112
  return { kind: 'not-found' };
126
113
  }
127
114
 
128
- const isShared = clients.some(
129
- (view) => view.name === client.name && view.clientID !== client.clientID,
130
- );
131
-
132
- return isShared
133
- ? { kind: 'ambiguous', name: client.name }
134
- : { kind: 'remove', name: client.name };
115
+ throw error;
135
116
  }
136
117
 
137
118
  function toAuthGrantView(grant: GrantView): AuthGrantView {
@@ -1,6 +1,5 @@
1
- import { DEFAULT_PATHS } from '@zgeoff/mcp-auth';
1
+ import { DEFAULT_PATHS, normalizeClientName } from '@zgeoff/mcp-auth';
2
2
  import type { SecurityEvent } from '@zgeoff/mcp-auth';
3
- import { normalizeClientName } from './normalize-client-name.ts';
4
3
 
5
4
  // The IDs in an event are tagged strings, and a line needs only their text.
6
5
  type LoggedSecurityEvent = Omit<SecurityEvent, 'client' | 'correlationID'> & {
@@ -1 +0,0 @@
1
- export declare function normalizeClientName(raw: unknown, fallback?: string, maxLength?: number): string;
@@ -1,20 +0,0 @@
1
- export function normalizeClientName(
2
- raw: unknown,
3
- fallback = 'unnamed client',
4
- maxLength = 100,
5
- ): string {
6
- if (typeof raw !== 'string') {
7
- return fallback;
8
- }
9
-
10
- const folded = raw
11
- .replaceAll(/[\t\n\v\f\r]/gu, ' ')
12
- .replaceAll(/[\p{Cc}\p{Cf}]/gu, '')
13
- .replaceAll(/\s+/gu, ' ')
14
- .trim();
15
-
16
- // The cut counts code points, so it never splits a surrogate pair.
17
- const cut = folded.replace(new RegExp(`^(?<kept>.{${maxLength}}).+$`, 'su'), '$<kept>').trim();
18
-
19
- return cut === '' ? fallback : cut;
20
- }