@mi9-identity/auth-middleware-express 1.0.0
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/CHANGELOG.md +35 -0
- package/LICENSE +201 -0
- package/README.md +234 -0
- package/dist/adapter.d.ts +12 -0
- package/dist/adapter.js +89 -0
- package/dist/default-error-mapping.d.ts +11 -0
- package/dist/default-error-mapping.js +124 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +45 -0
- package/dist/event.d.ts +50 -0
- package/dist/event.js +83 -0
- package/dist/failure-reason.d.ts +12 -0
- package/dist/failure-reason.js +44 -0
- package/dist/guards.d.ts +47 -0
- package/dist/guards.js +180 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +5 -0
- package/dist/type-guards.d.ts +93 -0
- package/dist/type-guards.js +102 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +2 -0
- package/package.json +73 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrowing type guards, duplicated per package by design (see CLAUDE.md) —
|
|
3
|
+
* mirrored from the identity service's canonical `utils/type-guards.ts`, plus
|
|
4
|
+
* a package-local `isFunction` (the canonical set has no function guard).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict null check.
|
|
8
|
+
*
|
|
9
|
+
* @param { unknown } value Value to test.
|
|
10
|
+
* @returns { value is null } True iff `value === null`.
|
|
11
|
+
* @example
|
|
12
|
+
* isNull(null); // true
|
|
13
|
+
* isNull(undefined); // false
|
|
14
|
+
* isNull(0); // false
|
|
15
|
+
* isNull(''); // false
|
|
16
|
+
*/
|
|
17
|
+
export declare const isNull: (value: unknown) => value is null;
|
|
18
|
+
/**
|
|
19
|
+
* Strict undefined check.
|
|
20
|
+
*
|
|
21
|
+
* @param { unknown } value Value to test.
|
|
22
|
+
* @returns { value is undefined } True iff `value === undefined`.
|
|
23
|
+
* @example
|
|
24
|
+
* isUndefined(undefined); // true
|
|
25
|
+
* isUndefined(null); // false
|
|
26
|
+
* isUndefined(0); // false
|
|
27
|
+
*/
|
|
28
|
+
export declare const isUndefined: (value: unknown) => value is undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Nullish check (null or undefined).
|
|
31
|
+
*
|
|
32
|
+
* @param { unknown } value Value to test.
|
|
33
|
+
* @returns { value is null | undefined } True iff `value` is null or undefined.
|
|
34
|
+
* @example
|
|
35
|
+
* isNil(null); // true
|
|
36
|
+
* isNil(undefined); // true
|
|
37
|
+
* isNil(0); // false
|
|
38
|
+
* isNil(''); // false
|
|
39
|
+
*/
|
|
40
|
+
export declare const isNil: (value: unknown) => value is null | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Primitive string check.
|
|
43
|
+
*
|
|
44
|
+
* @param { unknown } value Value to test.
|
|
45
|
+
* @returns { value is string } True iff `typeof value === 'string'`.
|
|
46
|
+
* @example
|
|
47
|
+
* isString('hello'); // true
|
|
48
|
+
* isString(''); // true
|
|
49
|
+
* isString(42); // false
|
|
50
|
+
* isString(new String('hello')); // false (boxed String, not primitive)
|
|
51
|
+
*/
|
|
52
|
+
export declare const isString: (value: unknown) => value is string;
|
|
53
|
+
/**
|
|
54
|
+
* Narrowing return type for `isEmpty`. Listed unions are the structural
|
|
55
|
+
* "empty" shapes; the negation `!isEmpty(x)` strips `null | undefined`
|
|
56
|
+
* (and the empty literal forms where TS retains discrimination), so a
|
|
57
|
+
* `string | undefined` source narrows to `string` after a non-empty check.
|
|
58
|
+
*/
|
|
59
|
+
type EmptyValue = null | undefined | '' | readonly never[] | Map<unknown, never> | Set<never> | Record<string, never>;
|
|
60
|
+
/**
|
|
61
|
+
* Empty-shape check (nullish, empty string, array, Map, Set, or plain object). Numbers and booleans are never empty.
|
|
62
|
+
*
|
|
63
|
+
* @param { unknown } value Value to test.
|
|
64
|
+
* @returns { value is EmptyValue } True iff `value` is an empty shape.
|
|
65
|
+
* @example
|
|
66
|
+
* isEmpty(null); // true
|
|
67
|
+
* isEmpty(undefined); // true
|
|
68
|
+
* isEmpty(''); // true
|
|
69
|
+
* isEmpty([]); // true
|
|
70
|
+
* isEmpty({}); // true
|
|
71
|
+
* isEmpty(new Map()); // true
|
|
72
|
+
* isEmpty(new Set()); // true
|
|
73
|
+
* isEmpty('hello'); // false
|
|
74
|
+
* isEmpty(0); // false
|
|
75
|
+
* isEmpty(false); // false
|
|
76
|
+
* isEmpty([0]); // false
|
|
77
|
+
* isEmpty({ a: undefined }); // false (key still counted)
|
|
78
|
+
*/
|
|
79
|
+
export declare const isEmpty: (value: unknown) => value is EmptyValue;
|
|
80
|
+
/**
|
|
81
|
+
* Callable check. Narrowing preserves the callable member of a union so the
|
|
82
|
+
* value stays invocable with its original signature.
|
|
83
|
+
*
|
|
84
|
+
* @param { unknown } value Value to test.
|
|
85
|
+
* @returns { value is (...args: never[]) => unknown } True iff `typeof value === 'function'`.
|
|
86
|
+
* @example
|
|
87
|
+
* isFunction(() => 1); // true
|
|
88
|
+
* isFunction(class {}); // true (classes are callable)
|
|
89
|
+
* isFunction(42); // false
|
|
90
|
+
*/
|
|
91
|
+
export declare const isFunction: (value: unknown) => value is (...args: never[]) => unknown;
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=type-guards.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrowing type guards, duplicated per package by design (see CLAUDE.md) —
|
|
3
|
+
* mirrored from the identity service's canonical `utils/type-guards.ts`, plus
|
|
4
|
+
* a package-local `isFunction` (the canonical set has no function guard).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict null check.
|
|
8
|
+
*
|
|
9
|
+
* @param { unknown } value Value to test.
|
|
10
|
+
* @returns { value is null } True iff `value === null`.
|
|
11
|
+
* @example
|
|
12
|
+
* isNull(null); // true
|
|
13
|
+
* isNull(undefined); // false
|
|
14
|
+
* isNull(0); // false
|
|
15
|
+
* isNull(''); // false
|
|
16
|
+
*/
|
|
17
|
+
export const isNull = (value) => value === null;
|
|
18
|
+
/**
|
|
19
|
+
* Strict undefined check.
|
|
20
|
+
*
|
|
21
|
+
* @param { unknown } value Value to test.
|
|
22
|
+
* @returns { value is undefined } True iff `value === undefined`.
|
|
23
|
+
* @example
|
|
24
|
+
* isUndefined(undefined); // true
|
|
25
|
+
* isUndefined(null); // false
|
|
26
|
+
* isUndefined(0); // false
|
|
27
|
+
*/
|
|
28
|
+
export const isUndefined = (value) => value === undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Nullish check (null or undefined).
|
|
31
|
+
*
|
|
32
|
+
* @param { unknown } value Value to test.
|
|
33
|
+
* @returns { value is null | undefined } True iff `value` is null or undefined.
|
|
34
|
+
* @example
|
|
35
|
+
* isNil(null); // true
|
|
36
|
+
* isNil(undefined); // true
|
|
37
|
+
* isNil(0); // false
|
|
38
|
+
* isNil(''); // false
|
|
39
|
+
*/
|
|
40
|
+
export const isNil = (value) => isNull(value) || isUndefined(value);
|
|
41
|
+
/**
|
|
42
|
+
* Primitive string check.
|
|
43
|
+
*
|
|
44
|
+
* @param { unknown } value Value to test.
|
|
45
|
+
* @returns { value is string } True iff `typeof value === 'string'`.
|
|
46
|
+
* @example
|
|
47
|
+
* isString('hello'); // true
|
|
48
|
+
* isString(''); // true
|
|
49
|
+
* isString(42); // false
|
|
50
|
+
* isString(new String('hello')); // false (boxed String, not primitive)
|
|
51
|
+
*/
|
|
52
|
+
export const isString = (value) => typeof value === 'string';
|
|
53
|
+
/**
|
|
54
|
+
* Empty-shape check (nullish, empty string, array, Map, Set, or plain object). Numbers and booleans are never empty.
|
|
55
|
+
*
|
|
56
|
+
* @param { unknown } value Value to test.
|
|
57
|
+
* @returns { value is EmptyValue } True iff `value` is an empty shape.
|
|
58
|
+
* @example
|
|
59
|
+
* isEmpty(null); // true
|
|
60
|
+
* isEmpty(undefined); // true
|
|
61
|
+
* isEmpty(''); // true
|
|
62
|
+
* isEmpty([]); // true
|
|
63
|
+
* isEmpty({}); // true
|
|
64
|
+
* isEmpty(new Map()); // true
|
|
65
|
+
* isEmpty(new Set()); // true
|
|
66
|
+
* isEmpty('hello'); // false
|
|
67
|
+
* isEmpty(0); // false
|
|
68
|
+
* isEmpty(false); // false
|
|
69
|
+
* isEmpty([0]); // false
|
|
70
|
+
* isEmpty({ a: undefined }); // false (key still counted)
|
|
71
|
+
*/
|
|
72
|
+
export const isEmpty = (value) => {
|
|
73
|
+
if (isNil(value)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (isString(value)) {
|
|
77
|
+
return value.length === 0;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
return value.length === 0;
|
|
81
|
+
}
|
|
82
|
+
if (value instanceof Map || value instanceof Set) {
|
|
83
|
+
return value.size === 0;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === 'object') {
|
|
86
|
+
return Object.keys(value).length === 0;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Callable check. Narrowing preserves the callable member of a union so the
|
|
92
|
+
* value stays invocable with its original signature.
|
|
93
|
+
*
|
|
94
|
+
* @param { unknown } value Value to test.
|
|
95
|
+
* @returns { value is (...args: never[]) => unknown } True iff `typeof value === 'function'`.
|
|
96
|
+
* @example
|
|
97
|
+
* isFunction(() => 1); // true
|
|
98
|
+
* isFunction(class {}); // true (classes are callable)
|
|
99
|
+
* isFunction(42); // false
|
|
100
|
+
*/
|
|
101
|
+
export const isFunction = (value) => typeof value === 'function';
|
|
102
|
+
//# sourceMappingURL=type-guards.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Verifier } from '@mi9-identity/jwt-verifier';
|
|
2
|
+
import type { VerifierError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Structured event the middleware emits on every auth attempt. Field shape
|
|
5
|
+
* is a CONTRACT — BigQuery sinks consume it directly, and the issuer's audit
|
|
6
|
+
* pipeline mirrors these field names. Don't rename or drop.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthEvent {
|
|
9
|
+
event: 'auth_succeeded' | 'auth_failed';
|
|
10
|
+
request_id: string;
|
|
11
|
+
client_id?: string;
|
|
12
|
+
retailer_code?: string;
|
|
13
|
+
product?: string;
|
|
14
|
+
success: boolean;
|
|
15
|
+
latency_ms: number;
|
|
16
|
+
jti?: string;
|
|
17
|
+
failure_reason?: 'missing_token' | 'invalid_token' | 'token_expired' | 'issuer_not_allowed' | 'audience_mismatch' | 'algorithm_not_allowed' | 'missing_required_claim' | 'insufficient_scope' | 'retailer_mismatch' | 'jwks_unavailable';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Shape returned by `onError`. The adapter writes `status`, optionally sets
|
|
21
|
+
* `headers`, and JSON-encodes `body`.
|
|
22
|
+
*/
|
|
23
|
+
export interface ErrorResponse {
|
|
24
|
+
status: number;
|
|
25
|
+
body: unknown;
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for `mi9Auth`. The verifier is required; everything else is
|
|
30
|
+
* defaulted. Pass `onError` to override the RFC 6750 mapping with your own
|
|
31
|
+
* envelope (e.g. matching an existing API error contract).
|
|
32
|
+
*/
|
|
33
|
+
export interface Mi9AuthOptions {
|
|
34
|
+
verifier: Verifier;
|
|
35
|
+
/** Defaults to `'authorization'`. Case-insensitive — Express normalises. */
|
|
36
|
+
headerName?: string;
|
|
37
|
+
/** Defaults to `'X-Request-ID'`. Read on inbound (lookup is lowercased per Express convention), propagated to `AuthEvent.request_id`. */
|
|
38
|
+
requestIdHeader?: string;
|
|
39
|
+
/** Defaults to no-op. Wire your structured logger here. */
|
|
40
|
+
onAuthEvent?: (e: AuthEvent) => void;
|
|
41
|
+
/** Defaults to RFC 6750 §3 mapping (see `default-error-mapping.ts`). */
|
|
42
|
+
onError?: (err: VerifierError) => ErrorResponse;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mi9-identity/auth-middleware-express",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Express adapter for the Mi9 JWT verifier — drop-in middleware that verifies Mi9-issued JWTs and attaches AuthContext to req.auth.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mi9",
|
|
8
|
+
"identity",
|
|
9
|
+
"oauth2",
|
|
10
|
+
"jwt",
|
|
11
|
+
"rs256",
|
|
12
|
+
"jwks",
|
|
13
|
+
"m2m",
|
|
14
|
+
"express"
|
|
15
|
+
],
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"author": "Mi9 Retail",
|
|
18
|
+
"homepage": "https://mi9retail.com",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=24.0.0"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/**/*.map",
|
|
34
|
+
"!dist/.tsbuildinfo",
|
|
35
|
+
"README.md",
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@types/express": "^5.0.0",
|
|
44
|
+
"express": ">=5.0.0",
|
|
45
|
+
"@mi9-identity/jwt-verifier": "^1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@types/express": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/express": "5.0.6",
|
|
54
|
+
"@types/node": "24.13.3",
|
|
55
|
+
"@types/supertest": "7.2.1",
|
|
56
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
57
|
+
"express": "5.2.1",
|
|
58
|
+
"supertest": "7.2.2",
|
|
59
|
+
"vitest": "4.1.11",
|
|
60
|
+
"@mi9-identity/jwt-verifier": "^1.0.0",
|
|
61
|
+
"@mi9-identity/tsconfig": "^1.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsc -b",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"test:cov": "vitest run --coverage",
|
|
69
|
+
"lint": "biome lint .",
|
|
70
|
+
"lint:fix": "biome lint --write .",
|
|
71
|
+
"clean": "rimraf dist .turbo"
|
|
72
|
+
}
|
|
73
|
+
}
|