@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +3 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +2 -0
- package/package.json +1 -1
- package/src/OxyServices.ts +9 -0
- package/src/__tests__/publicInterfaceParity.test.ts +75 -0
- package/src/mixins/OxyServices.utility.ts +2 -0
|
@@ -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
package/src/OxyServices.ts
CHANGED
|
@@ -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;
|