@oxy.so/core 1.5.0 → 1.5.1

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.
@@ -59,6 +59,7 @@
59
59
  import { type LinkedHttpClient, type OxyConfig } from './OxyServices.base';
60
60
  import { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError, ServiceAssetMetadataError } from './OxyServices.errors';
61
61
  import { composeOxyServices } from './mixins';
62
+ import type { OxyAuthRefusal } from './mixins/OxyServices.utility';
62
63
  /**
63
64
  * OxyServices - Unified client library for interacting with the Oxy API
64
65
  *
@@ -105,6 +106,7 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
105
106
  onError?: (error: unknown) => unknown;
106
107
  loadUser?: boolean;
107
108
  optional?: boolean;
109
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
108
110
  serviceTokenJwksUrl?: string;
109
111
  jwtSecret?: string;
110
112
  expectedIssuer?: string;
@@ -115,6 +117,7 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
115
117
  }): (socket: unknown, next: (err?: Error) => void) => Promise<void>;
116
118
  serviceAuth(options?: {
117
119
  debug?: boolean;
120
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
118
121
  serviceTokenJwksUrl?: string;
119
122
  jwtSecret?: string;
120
123
  expectedIssuer?: string;
@@ -290,6 +290,8 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
290
290
  */
291
291
  serviceAuth(options?: {
292
292
  debug?: boolean;
293
+ /** Observe refusals on the service lane; forwarded straight to `auth()`. */
294
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
293
295
  /** @deprecated Oxy-API-only HS256 transition; external verifiers use JWKS. */
294
296
  jwtSecret?: string;
295
297
  serviceTokenJwksUrl?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -61,6 +61,7 @@ import { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeo
61
61
 
62
62
  // Import mixin composition helper
63
63
  import { composeOxyServices } from './mixins';
64
+ import type { OxyAuthRefusal } from './mixins/OxyServices.utility';
64
65
 
65
66
  /**
66
67
  * OxyServices - Unified client library for interacting with the Oxy API
@@ -120,11 +121,18 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
120
121
  createLinkedClient(config: OxyConfig): LinkedHttpClient;
121
122
 
122
123
  // Express.js middleware
124
+ //
125
+ // This list is the one CONSUMERS see: `OxyAuthMiddlewareOptions` in
126
+ // `server/auth.ts` is `Parameters<OxyServices['auth']>[0]`, so an option that
127
+ // exists on the mixin and not here does not exist for anybody outside this
128
+ // package. `onRefusal` shipped in 1.5.0 exactly that way — present in the
129
+ // implementation, invisible to every host that tried to pass it.
123
130
  auth(options?: {
124
131
  debug?: boolean;
125
132
  onError?: (error: unknown) => unknown;
126
133
  loadUser?: boolean;
127
134
  optional?: boolean;
135
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
128
136
  serviceTokenJwksUrl?: string;
129
137
  jwtSecret?: string;
130
138
  expectedIssuer?: string;
@@ -139,6 +147,7 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
139
147
  // Service-token-only middleware (delegates to auth() internally)
140
148
  serviceAuth(options?: {
141
149
  debug?: boolean;
150
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
142
151
  serviceTokenJwksUrl?: string;
143
152
  jwtSecret?: string;
144
153
  expectedIssuer?: string;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The middleware options a CONSUMER can pass must be the ones the middleware
3
+ * actually takes.
4
+ *
5
+ * `src/OxyServices.ts` re-declares `auth()` and `serviceAuth()` by hand, with
6
+ * their option objects written out inline, because declaration emit for the
7
+ * mixin pipeline does not carry them. That hand-written interface is what
8
+ * downstream packages see — `OxyAuthMiddlewareOptions` in `server/auth.ts` is
9
+ * literally `Parameters<OxyServices['auth']>[0]` — so an option that exists on
10
+ * the mixin and not there exists for this package and for nobody else.
11
+ *
12
+ * `onRefusal` shipped in 1.5.0 exactly that way: implemented, tested, exported,
13
+ * and rejected by the first host that tried to pass it
14
+ * (`TS2345 … has no properties in common with`). Nothing in this package could
15
+ * see it, because everything in this package resolves the mixin's own
16
+ * declaration, where the option is present. Only the published `.d.ts` loses it.
17
+ *
18
+ * So the check is on the SOURCE TEXT of the two declarations, which is the
19
+ * thing that diverges. It is not a type test: a type test here reads the merged
20
+ * interface and passes either way — measured, by deleting the option and
21
+ * watching `tsc --noEmit` stay green.
22
+ */
23
+
24
+ import { readFileSync } from 'node:fs';
25
+ import { join } from 'node:path';
26
+
27
+ const SRC = join(__dirname, '..');
28
+
29
+ /** Every `name?:` key declared directly inside one brace-balanced block. */
30
+ function optionKeys(source: string, startPattern: RegExp): string[] {
31
+ const match = startPattern.exec(source);
32
+ if (!match) throw new Error(`declaration not found: ${String(startPattern)}`);
33
+ let depth = 0;
34
+ let index = source.indexOf('{', match.index);
35
+ const open = index;
36
+ do {
37
+ const char = source[index];
38
+ if (char === '{') depth += 1;
39
+ else if (char === '}') depth -= 1;
40
+ index += 1;
41
+ } while (depth > 0 && index < source.length);
42
+ const body = source.slice(open + 1, index - 1);
43
+ // Only keys at THIS level: a nested object literal would otherwise contribute
44
+ // its own keys. None exist today; the guard keeps that from becoming silent.
45
+ const withoutNested = body.replace(/\{[^{}]*\}/g, '');
46
+ return [...withoutNested.matchAll(/^\s*(\w+)\??\s*:/gm)].map((m) => m[1] as string).sort();
47
+ }
48
+
49
+ describe('the public OxyServices interface declares the options the mixin takes', () => {
50
+ const mixin = readFileSync(join(SRC, 'mixins/OxyServices.utility.ts'), 'utf8');
51
+ const publicInterface = readFileSync(join(SRC, 'OxyServices.ts'), 'utf8');
52
+
53
+ it('auth() — same option names on both declarations', () => {
54
+ const declared = optionKeys(mixin, /interface AuthMiddlewareOptions/);
55
+ const published = optionKeys(publicInterface, /^ {2}auth\(options\?: \{/m);
56
+
57
+ expect(declared).toContain('onRefusal');
58
+ expect(published).toEqual(declared);
59
+ });
60
+
61
+ it('serviceAuth() — same option names on both declarations', () => {
62
+ const declared = optionKeys(mixin, /^ {4}serviceAuth\(options: \{/m);
63
+ const published = optionKeys(publicInterface, /^ {2}serviceAuth\(options\?: \{/m);
64
+
65
+ expect(declared).toContain('onRefusal');
66
+ expect(published).toEqual(declared);
67
+ });
68
+
69
+ it('reads real declarations rather than matching nothing', () => {
70
+ // The floor. A regex that stopped matching would make both cases above
71
+ // compare two empty lists and pass.
72
+ expect(optionKeys(mixin, /interface AuthMiddlewareOptions/).length).toBeGreaterThan(5);
73
+ expect(optionKeys(publicInterface, /^ {2}auth\(options\?: \{/m).length).toBeGreaterThan(5);
74
+ });
75
+ });
@@ -1178,6 +1178,8 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
1178
1178
  */
1179
1179
  serviceAuth(options: {
1180
1180
  debug?: boolean;
1181
+ /** Observe refusals on the service lane; forwarded straight to `auth()`. */
1182
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
1181
1183
  /** @deprecated Oxy-API-only HS256 transition; external verifiers use JWKS. */
1182
1184
  jwtSecret?: string;
1183
1185
  serviceTokenJwksUrl?: string;