@easyweb/authentication 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/README.md +100 -0
- package/dist/errors/bad-request-error.d.ts +10 -0
- package/dist/errors/bad-request-error.d.ts.map +1 -0
- package/dist/errors/bad-request-error.js +16 -0
- package/dist/errors/bad-request-error.js.map +1 -0
- package/dist/errors/custom-error.d.ts +9 -0
- package/dist/errors/custom-error.d.ts.map +1 -0
- package/dist/errors/custom-error.js +11 -0
- package/dist/errors/custom-error.js.map +1 -0
- package/dist/errors/database-connection-error.d.ts +10 -0
- package/dist/errors/database-connection-error.d.ts.map +1 -0
- package/dist/errors/database-connection-error.js +17 -0
- package/dist/errors/database-connection-error.js.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +22 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/not-authorized-error.d.ts +9 -0
- package/dist/errors/not-authorized-error.d.ts.map +1 -0
- package/dist/errors/not-authorized-error.js +16 -0
- package/dist/errors/not-authorized-error.js.map +1 -0
- package/dist/errors/not-found-error.d.ts +9 -0
- package/dist/errors/not-found-error.d.ts.map +1 -0
- package/dist/errors/not-found-error.js +16 -0
- package/dist/errors/not-found-error.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt/index.d.ts +2 -0
- package/dist/jwt/index.d.ts.map +1 -0
- package/dist/jwt/index.js +18 -0
- package/dist/jwt/index.js.map +1 -0
- package/dist/jwt/verify.d.ts +18 -0
- package/dist/jwt/verify.d.ts.map +1 -0
- package/dist/jwt/verify.js +38 -0
- package/dist/jwt/verify.js.map +1 -0
- package/dist/middleware/authenticate.d.ts +52 -0
- package/dist/middleware/authenticate.d.ts.map +1 -0
- package/dist/middleware/authenticate.js +95 -0
- package/dist/middleware/authenticate.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +19 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/request-context.d.ts +15 -0
- package/dist/middleware/request-context.d.ts.map +1 -0
- package/dist/middleware/request-context.js +19 -0
- package/dist/middleware/request-context.js.map +1 -0
- package/dist/types/jwt.d.ts +33 -0
- package/dist/types/jwt.d.ts.map +1 -0
- package/dist/types/jwt.js +3 -0
- package/dist/types/jwt.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @easyweb/authentication
|
|
2
|
+
|
|
3
|
+
Shared authentication primitives for Easyweb microservices — stateless JWT middleware,
|
|
4
|
+
request-context middleware, and the HTTP error classes every service throws.
|
|
5
|
+
|
|
6
|
+
Companion to [`@easyweb/rabbitmq-utils`](https://www.npmjs.com/package/@easyweb/rabbitmq-utils),
|
|
7
|
+
which covers events and observability.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @easyweb/authentication
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`express@^5` and `jsonwebtoken@^9` are peer dependencies — the consuming service
|
|
16
|
+
provides them.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Nothing in this package reads `process.env`. Config is bound once at startup, so the
|
|
21
|
+
package never has to reach into a service's `config` module.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import express from "express";
|
|
25
|
+
import cookieParser from "cookie-parser";
|
|
26
|
+
import {
|
|
27
|
+
createAuthMiddleware,
|
|
28
|
+
requestContext,
|
|
29
|
+
NotAuthorizedError,
|
|
30
|
+
} from "@easyweb/authentication";
|
|
31
|
+
import config from "./config";
|
|
32
|
+
|
|
33
|
+
const app = express();
|
|
34
|
+
app.use(cookieParser()); // required: the middleware reads the accessToken cookie
|
|
35
|
+
app.use(requestContext);
|
|
36
|
+
|
|
37
|
+
const authMiddleware = createAuthMiddleware(config.jwt);
|
|
38
|
+
// config.jwt must supply { secret, issuer, audience } matching auth-service's
|
|
39
|
+
|
|
40
|
+
app.get("/me", authMiddleware.authenticate, (req, res) => {
|
|
41
|
+
res.json(req.user); // { id, email, username, roles, permissions }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.get("/feed", authMiddleware.optionalAuth, (req, res) => {
|
|
45
|
+
res.json({ personalised: Boolean(req.user) });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.delete(
|
|
49
|
+
"/admin/:id",
|
|
50
|
+
authMiddleware.authenticate,
|
|
51
|
+
authMiddleware.authorize(["admin"]),
|
|
52
|
+
handler,
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Minting tokens (auth-service only):
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createJwtSigner } from "@easyweb/authentication";
|
|
60
|
+
|
|
61
|
+
const signAccessToken = createJwtSigner(config.jwt);
|
|
62
|
+
// config.jwt additionally needs accessTokenTtlSeconds
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What it verifies — and what it does not
|
|
66
|
+
|
|
67
|
+
`authenticate` is **stateless**. It checks the signature, issuer, audience, expiry, and
|
|
68
|
+
that the payload is a well-formed access token. It performs no database lookup.
|
|
69
|
+
|
|
70
|
+
A logout or session revoke in auth-service stays invisible here until the access token
|
|
71
|
+
expires (`JWT_ACCESS_TOKEN_TTL_SECONDS`, currently 300). If a service needs immediate
|
|
72
|
+
revocation, it must keep its own DB-backed middleware — auth-service does exactly that.
|
|
73
|
+
|
|
74
|
+
`req.user.permissions` is always `[]`. The access token carries no `permissions` claim;
|
|
75
|
+
auth-service resolves those from its own tables at request time. Guarding on this array
|
|
76
|
+
would deny every request.
|
|
77
|
+
|
|
78
|
+
## Exports
|
|
79
|
+
|
|
80
|
+
| Export | Purpose |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `createAuthMiddleware(config)` | `{ authenticate, optionalAuth, authorize }` |
|
|
83
|
+
| `requestContext` | propagates / generates `x-request-id` |
|
|
84
|
+
| `createJwtVerifier(config)` | `(token) => JwtAccessPayload`, throws on invalid |
|
|
85
|
+
| `createJwtSigner(config)` | `(claims) => string` |
|
|
86
|
+
| `CustomError`, `BadRequestError`, `NotAuthorizedError`, `NotFoundError`, `DatabaseConnectionError` | shared error hierarchy |
|
|
87
|
+
| `JwtAccessPayload`, `JwtAccessClaims`, `JwtVerifyConfig`, `JwtSignConfig` | types |
|
|
88
|
+
|
|
89
|
+
Importing the package also augments `Express.Request` with `user`, `auth`, and
|
|
90
|
+
`requestId`. Remove any local copy of that `declare global` block or TypeScript will
|
|
91
|
+
report duplicate members.
|
|
92
|
+
|
|
93
|
+
## Token contract
|
|
94
|
+
|
|
95
|
+
`JwtAccessPayload` is the only shape the services agree on. Changing it breaks every
|
|
96
|
+
consumer — bump the major version.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CustomError } from "./custom-error";
|
|
2
|
+
declare class BadRequestError extends CustomError {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
serializeErrors(): {
|
|
6
|
+
message: string;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
export { BadRequestError };
|
|
10
|
+
//# sourceMappingURL=bad-request-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bad-request-error.d.ts","sourceRoot":"","sources":["../../src/errors/bad-request-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,cAAM,eAAgB,SAAQ,WAAW;IACvC,UAAU,SAAO;gBAEL,OAAO,EAAE,MAAM;IAK3B,eAAe;;;CAGhB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BadRequestError = void 0;
|
|
4
|
+
const custom_error_1 = require("./custom-error");
|
|
5
|
+
class BadRequestError extends custom_error_1.CustomError {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.statusCode = 400;
|
|
9
|
+
Object.setPrototypeOf(this, BadRequestError.prototype);
|
|
10
|
+
}
|
|
11
|
+
serializeErrors() {
|
|
12
|
+
return [{ message: this.message }];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.BadRequestError = BadRequestError;
|
|
16
|
+
//# sourceMappingURL=bad-request-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bad-request-error.js","sourceRoot":"","sources":["../../src/errors/bad-request-error.ts"],"names":[],"mappings":";;;AAAA,iDAA6C;AAE7C,MAAM,eAAgB,SAAQ,0BAAW;IAGvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHjB,eAAU,GAAG,GAAG,CAAC;QAIf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACzD,CAAC;IAED,eAAe;QACb,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC;CACF;AAEQ,0CAAe"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-error.d.ts","sourceRoot":"","sources":["../../src/errors/custom-error.ts"],"names":[],"mappings":"AAAA,8BAAsB,WAAY,SAAQ,KAAK;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,MAAM;IAK3B,QAAQ,CAAC,eAAe,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE;CAClE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomError = void 0;
|
|
4
|
+
class CustomError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
Object.setPrototypeOf(this, CustomError.prototype);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.CustomError = CustomError;
|
|
11
|
+
//# sourceMappingURL=custom-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-error.js","sourceRoot":"","sources":["../../src/errors/custom-error.ts"],"names":[],"mappings":";;;AAAA,MAAsB,WAAY,SAAQ,KAAK;IAG7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CAGF;AATD,kCASC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CustomError } from "./custom-error";
|
|
2
|
+
export declare class DatabaseConnectionError extends CustomError {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
reason: string;
|
|
5
|
+
constructor();
|
|
6
|
+
serializeErrors(): {
|
|
7
|
+
message: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=database-connection-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-connection-error.d.ts","sourceRoot":"","sources":["../../src/errors/database-connection-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,qBAAa,uBAAwB,SAAQ,WAAW;IACtD,UAAU,SAAO;IACjB,MAAM,SAAkC;;IAQxC,eAAe;;;CAGhB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabaseConnectionError = void 0;
|
|
4
|
+
const custom_error_1 = require("./custom-error");
|
|
5
|
+
class DatabaseConnectionError extends custom_error_1.CustomError {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Error connecting to database");
|
|
8
|
+
this.statusCode = 500;
|
|
9
|
+
this.reason = "Error connecting to database";
|
|
10
|
+
Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
|
|
11
|
+
}
|
|
12
|
+
serializeErrors() {
|
|
13
|
+
return [{ message: this.reason }];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.DatabaseConnectionError = DatabaseConnectionError;
|
|
17
|
+
//# sourceMappingURL=database-connection-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-connection-error.js","sourceRoot":"","sources":["../../src/errors/database-connection-error.ts"],"names":[],"mappings":";;;AAAA,iDAA6C;AAE7C,MAAa,uBAAwB,SAAQ,0BAAW;IAItD;QACE,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAJxC,eAAU,GAAG,GAAG,CAAC;QACjB,WAAM,GAAG,8BAA8B,CAAC;QAKtC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACjE,CAAC;IAED,eAAe;QACb,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC;CACF;AAbD,0DAaC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./custom-error"), exports);
|
|
18
|
+
__exportStar(require("./bad-request-error"), exports);
|
|
19
|
+
__exportStar(require("./not-authorized-error"), exports);
|
|
20
|
+
__exportStar(require("./not-found-error"), exports);
|
|
21
|
+
__exportStar(require("./database-connection-error"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,sDAAoC;AACpC,yDAAuC;AACvC,oDAAkC;AAClC,8DAA4C"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CustomError } from "./custom-error";
|
|
2
|
+
export declare class NotAuthorizedError extends CustomError {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
constructor(message?: string);
|
|
5
|
+
serializeErrors(): {
|
|
6
|
+
message: string;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=not-authorized-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-authorized-error.d.ts","sourceRoot":"","sources":["../../src/errors/not-authorized-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,qBAAa,kBAAmB,SAAQ,WAAW;IACjD,UAAU,SAAO;gBAEL,OAAO,GAAE,MAAyB;IAK9C,eAAe;;;CAGhB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NotAuthorizedError = void 0;
|
|
4
|
+
const custom_error_1 = require("./custom-error");
|
|
5
|
+
class NotAuthorizedError extends custom_error_1.CustomError {
|
|
6
|
+
constructor(message = "Not authorized") {
|
|
7
|
+
super(message);
|
|
8
|
+
this.statusCode = 401;
|
|
9
|
+
Object.setPrototypeOf(this, NotAuthorizedError.prototype);
|
|
10
|
+
}
|
|
11
|
+
serializeErrors() {
|
|
12
|
+
return [{ message: this.message }];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.NotAuthorizedError = NotAuthorizedError;
|
|
16
|
+
//# sourceMappingURL=not-authorized-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-authorized-error.js","sourceRoot":"","sources":["../../src/errors/not-authorized-error.ts"],"names":[],"mappings":";;;AAAA,iDAA6C;AAE7C,MAAa,kBAAmB,SAAQ,0BAAW;IAGjD,YAAY,UAAkB,gBAAgB;QAC5C,KAAK,CAAC,OAAO,CAAC,CAAC;QAHjB,eAAU,GAAG,GAAG,CAAC;QAIf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED,eAAe;QACb,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC;CACF;AAXD,gDAWC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-found-error.d.ts","sourceRoot":"","sources":["../../src/errors/not-found-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,qBAAa,aAAc,SAAQ,WAAW;IAC5C,UAAU,SAAO;;IAOjB,eAAe;;;CAGhB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NotFoundError = void 0;
|
|
4
|
+
const custom_error_1 = require("./custom-error");
|
|
5
|
+
class NotFoundError extends custom_error_1.CustomError {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Route not found");
|
|
8
|
+
this.statusCode = 404;
|
|
9
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
10
|
+
}
|
|
11
|
+
serializeErrors() {
|
|
12
|
+
return [{ message: "Not Found" }];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.NotFoundError = NotFoundError;
|
|
16
|
+
//# sourceMappingURL=not-found-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-found-error.js","sourceRoot":"","sources":["../../src/errors/not-found-error.ts"],"names":[],"mappings":";;;AAAA,iDAA6C;AAE7C,MAAa,aAAc,SAAQ,0BAAW;IAG5C;QACE,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAH3B,eAAU,GAAG,GAAG,CAAC;QAIf,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,eAAe;QACb,OAAO,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;IACpC,CAAC;CACF;AAXD,sCAWC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./errors"), exports);
|
|
18
|
+
__exportStar(require("./jwt"), exports);
|
|
19
|
+
__exportStar(require("./middleware"), exports);
|
|
20
|
+
__exportStar(require("./types/jwt"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,wCAAsB;AACtB,+CAA6B;AAC7B,8CAA4B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/jwt/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./verify"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/jwt/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JwtAccessClaims, JwtAccessPayload, JwtSignConfig, JwtVerifyConfig } from "../types/jwt";
|
|
2
|
+
/**
|
|
3
|
+
* Factory rather than a bare function: package code cannot reach a service's
|
|
4
|
+
* `config` module, so the secret/issuer/audience are bound once at startup.
|
|
5
|
+
*
|
|
6
|
+
* The returned verifier throws whatever `jsonwebtoken` throws
|
|
7
|
+
* (`JsonWebTokenError`, `TokenExpiredError`); callers translate that into
|
|
8
|
+
* `NotAuthorizedError`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createJwtVerifier(config: JwtVerifyConfig): (token: string) => JwtAccessPayload;
|
|
11
|
+
/**
|
|
12
|
+
* Only auth-service mints tokens. Every other service consumes them and needs
|
|
13
|
+
* `createJwtVerifier` alone.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createJwtSigner(config: JwtSignConfig): (payload: JwtAccessClaims) => string;
|
|
16
|
+
export type JwtVerifier = ReturnType<typeof createJwtVerifier>;
|
|
17
|
+
export type JwtSigner = ReturnType<typeof createJwtSigner>;
|
|
18
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/jwt/verify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EAChB,MAAM,cAAc,CAAC;AAEtB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,IACrB,OAAO,MAAM,KAAG,gBAAgB,CAMnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,IACnB,SAAS,eAAe,KAAG,MAAM,CAOlE;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC/D,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createJwtVerifier = createJwtVerifier;
|
|
7
|
+
exports.createJwtSigner = createJwtSigner;
|
|
8
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
9
|
+
/**
|
|
10
|
+
* Factory rather than a bare function: package code cannot reach a service's
|
|
11
|
+
* `config` module, so the secret/issuer/audience are bound once at startup.
|
|
12
|
+
*
|
|
13
|
+
* The returned verifier throws whatever `jsonwebtoken` throws
|
|
14
|
+
* (`JsonWebTokenError`, `TokenExpiredError`); callers translate that into
|
|
15
|
+
* `NotAuthorizedError`.
|
|
16
|
+
*/
|
|
17
|
+
function createJwtVerifier(config) {
|
|
18
|
+
return function verifyAccessToken(token) {
|
|
19
|
+
return jsonwebtoken_1.default.verify(token, config.secret, {
|
|
20
|
+
issuer: config.issuer,
|
|
21
|
+
audience: config.audience,
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Only auth-service mints tokens. Every other service consumes them and needs
|
|
27
|
+
* `createJwtVerifier` alone.
|
|
28
|
+
*/
|
|
29
|
+
function createJwtSigner(config) {
|
|
30
|
+
return function signAccessToken(payload) {
|
|
31
|
+
return jsonwebtoken_1.default.sign(payload, config.secret, {
|
|
32
|
+
expiresIn: config.accessTokenTtlSeconds,
|
|
33
|
+
issuer: config.issuer,
|
|
34
|
+
audience: config.audience,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/jwt/verify.ts"],"names":[],"mappings":";;;;;AAgBA,8CAOC;AAMD,0CAQC;AArCD,gEAA+B;AAQ/B;;;;;;;GAOG;AACH,SAAgB,iBAAiB,CAAC,MAAuB;IACvD,OAAO,SAAS,iBAAiB,CAAC,KAAa;QAC7C,OAAO,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;YACtC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAqB,CAAC;IACzB,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,MAAqB;IACnD,OAAO,SAAS,eAAe,CAAC,OAAwB;QACtD,OAAO,sBAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YACtC,SAAS,EAAE,MAAM,CAAC,qBAAqB;YACvC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACP,CAAC,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
import type { JwtVerifyConfig } from "../types/jwt";
|
|
3
|
+
/**
|
|
4
|
+
* Stateless verification. Unlike auth-service's own middleware there is no
|
|
5
|
+
* database round trip: the access token is verified against the shared secret
|
|
6
|
+
* and its claims are trusted as-is.
|
|
7
|
+
*
|
|
8
|
+
* TRADE-OFF — a logout or session revoke performed in auth-service is invisible
|
|
9
|
+
* here until the access token expires (JWT_ACCESS_TOKEN_TTL_SECONDS, currently
|
|
10
|
+
* 300). That window is accepted for now. The upgrade path is a shared Redis
|
|
11
|
+
* denylist checked after verification:
|
|
12
|
+
*
|
|
13
|
+
* if (await redis.get(`session:revoked:${decoded.sessionId}`)) throw ...
|
|
14
|
+
*
|
|
15
|
+
* which requires auth-service to start publishing those keys on logout/revoke.
|
|
16
|
+
* It does not today.
|
|
17
|
+
*
|
|
18
|
+
* Any service needing immediate revocation must keep its own DB-backed
|
|
19
|
+
* middleware instead of this one.
|
|
20
|
+
*/
|
|
21
|
+
declare global {
|
|
22
|
+
namespace Express {
|
|
23
|
+
interface Request {
|
|
24
|
+
user?: {
|
|
25
|
+
id: string;
|
|
26
|
+
email: string;
|
|
27
|
+
username: string | null;
|
|
28
|
+
roles: string[];
|
|
29
|
+
permissions: string[];
|
|
30
|
+
};
|
|
31
|
+
auth?: {
|
|
32
|
+
userId: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export type AuthMiddleware = {
|
|
39
|
+
/** Rejects with 401 unless the request carries a valid access token. */
|
|
40
|
+
authenticate: (req: Request, res: Response, next: NextFunction) => void;
|
|
41
|
+
/** Populates `req.user` when a valid token is present; never rejects. */
|
|
42
|
+
optionalAuth: (req: Request, res: Response, next: NextFunction) => void;
|
|
43
|
+
/** Role guard. Use after `authenticate`. */
|
|
44
|
+
authorize: (allowedRoles: string[]) => (req: Request, res: Response, next: NextFunction) => void;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Binds the JWT config once at startup and returns the middleware trio. The
|
|
48
|
+
* grouped return keeps call sites reading as `authMiddleware.authenticate`,
|
|
49
|
+
* matching auth-service's class-based middleware.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createAuthMiddleware(config: JwtVerifyConfig): AuthMiddleware;
|
|
52
|
+
//# sourceMappingURL=authenticate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../src/middleware/authenticate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,cAAc,CAAC;AAEtE;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE;gBACL,EAAE,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;gBACxB,KAAK,EAAE,MAAM,EAAE,CAAC;gBAChB,WAAW,EAAE,MAAM,EAAE,CAAC;aACvB,CAAC;YACF,IAAI,CAAC,EAAE;gBACL,MAAM,EAAE,MAAM,CAAC;gBACf,SAAS,CAAC,EAAE,MAAM,CAAC;aACpB,CAAC;SACH;KACF;CACF;AAoCD,MAAM,MAAM,cAAc,GAAG;IAC3B,wEAAwE;IACxE,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxE,yEAAyE;IACzE,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxE,4CAA4C;IAC5C,SAAS,EAAE,CACT,YAAY,EAAE,MAAM,EAAE,KACnB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,CAgF5E"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAuthMiddleware = createAuthMiddleware;
|
|
4
|
+
const verify_1 = require("../jwt/verify");
|
|
5
|
+
const not_authorized_error_1 = require("../errors/not-authorized-error");
|
|
6
|
+
/** Bearer header first, then the `accessToken` cookie auth-service sets. */
|
|
7
|
+
function extractToken(req) {
|
|
8
|
+
const authHeader = req.headers.authorization;
|
|
9
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
10
|
+
return authHeader.substring(7);
|
|
11
|
+
}
|
|
12
|
+
if (req.cookies && req.cookies.accessToken) {
|
|
13
|
+
return req.cookies.accessToken;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function attachClaims(req, decoded) {
|
|
18
|
+
req.user = {
|
|
19
|
+
id: decoded.sub,
|
|
20
|
+
email: decoded.email,
|
|
21
|
+
username: decoded.username ?? null,
|
|
22
|
+
roles: decoded.roles ?? [],
|
|
23
|
+
// The access token carries no `permissions` claim — auth-service resolves
|
|
24
|
+
// those from its own tables. Anything needing permission-level checks must
|
|
25
|
+
// either get the claim added upstream or query auth-service directly;
|
|
26
|
+
// guarding on this empty array would deny every request.
|
|
27
|
+
permissions: [],
|
|
28
|
+
};
|
|
29
|
+
req.auth = {
|
|
30
|
+
userId: decoded.sub,
|
|
31
|
+
sessionId: decoded.sessionId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Binds the JWT config once at startup and returns the middleware trio. The
|
|
36
|
+
* grouped return keeps call sites reading as `authMiddleware.authenticate`,
|
|
37
|
+
* matching auth-service's class-based middleware.
|
|
38
|
+
*/
|
|
39
|
+
function createAuthMiddleware(config) {
|
|
40
|
+
const verifyAccessToken = (0, verify_1.createJwtVerifier)(config);
|
|
41
|
+
/**
|
|
42
|
+
* Verifies the token and returns its claims, or null when anything about it
|
|
43
|
+
* is wrong — bad signature, wrong issuer/audience, expired, or a payload that
|
|
44
|
+
* is not a well-formed access token.
|
|
45
|
+
*/
|
|
46
|
+
function decodeAccessToken(token) {
|
|
47
|
+
let decoded;
|
|
48
|
+
try {
|
|
49
|
+
decoded = verifyAccessToken(token);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (!decoded ||
|
|
55
|
+
decoded.type !== "access" ||
|
|
56
|
+
!decoded.sub ||
|
|
57
|
+
!decoded.sessionId ||
|
|
58
|
+
!decoded.iat) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return decoded;
|
|
62
|
+
}
|
|
63
|
+
const authenticate = (req, _res, next) => {
|
|
64
|
+
const token = extractToken(req);
|
|
65
|
+
if (!token) {
|
|
66
|
+
throw new not_authorized_error_1.NotAuthorizedError();
|
|
67
|
+
}
|
|
68
|
+
const decoded = decodeAccessToken(token);
|
|
69
|
+
if (!decoded) {
|
|
70
|
+
throw new not_authorized_error_1.NotAuthorizedError();
|
|
71
|
+
}
|
|
72
|
+
attachClaims(req, decoded);
|
|
73
|
+
next();
|
|
74
|
+
};
|
|
75
|
+
const optionalAuth = (req, _res, next) => {
|
|
76
|
+
const token = extractToken(req);
|
|
77
|
+
if (token) {
|
|
78
|
+
const decoded = decodeAccessToken(token);
|
|
79
|
+
if (decoded)
|
|
80
|
+
attachClaims(req, decoded);
|
|
81
|
+
}
|
|
82
|
+
next();
|
|
83
|
+
};
|
|
84
|
+
const authorize = (allowedRoles) => {
|
|
85
|
+
return (req, _res, next) => {
|
|
86
|
+
const roles = req.user?.roles ?? [];
|
|
87
|
+
if (!roles.some((r) => allowedRoles.includes(r))) {
|
|
88
|
+
throw new not_authorized_error_1.NotAuthorizedError("Insufficient role");
|
|
89
|
+
}
|
|
90
|
+
next();
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
return { authenticate, optionalAuth, authorize };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=authenticate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticate.js","sourceRoot":"","sources":["../../src/middleware/authenticate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./authenticate"), exports);
|
|
18
|
+
__exportStar(require("./request-context"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,oDAAkC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Request {
|
|
5
|
+
requestId: string;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Picks up x-request-id from incoming headers (for inter-service forwarding),
|
|
11
|
+
* or generates a new UUID when the request originates externally.
|
|
12
|
+
* Always echoes the final value back in the response header.
|
|
13
|
+
*/
|
|
14
|
+
export declare function requestContext(req: Request, res: Response, next: NextFunction): void;
|
|
15
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/middleware/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,SAAS,EAAE,MAAM,CAAC;SACnB;KACF;CACF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,QAYnB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestContext = requestContext;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
/**
|
|
6
|
+
* Picks up x-request-id from incoming headers (for inter-service forwarding),
|
|
7
|
+
* or generates a new UUID when the request originates externally.
|
|
8
|
+
* Always echoes the final value back in the response header.
|
|
9
|
+
*/
|
|
10
|
+
function requestContext(req, res, next) {
|
|
11
|
+
const incoming = req.headers["x-request-id"];
|
|
12
|
+
const requestId = typeof incoming === "string" && incoming.length > 0
|
|
13
|
+
? incoming
|
|
14
|
+
: (0, crypto_1.randomUUID)();
|
|
15
|
+
req.requestId = requestId;
|
|
16
|
+
res.setHeader("x-request-id", requestId);
|
|
17
|
+
next();
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/middleware/request-context.ts"],"names":[],"mappings":";;AAgBA,wCAeC;AA9BD,mCAAoC;AAUpC;;;;GAIG;AACH,SAAgB,cAAc,CAC5B,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,MAAM,SAAS,GACb,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QACjD,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,IAAA,mBAAU,GAAE,CAAC;IAEnB,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IAC1B,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAEzC,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim set minted by auth-service's `signAccessTokenForSession`.
|
|
3
|
+
*
|
|
4
|
+
* This is the only shape the services agree on. Any change here is a breaking
|
|
5
|
+
* change for every consumer — bump the major version.
|
|
6
|
+
*
|
|
7
|
+
* Note there is no `permissions` claim: auth-service resolves permissions from
|
|
8
|
+
* its own tables at request time and does not put them in the token.
|
|
9
|
+
*/
|
|
10
|
+
export type JwtAccessPayload = {
|
|
11
|
+
sub: string;
|
|
12
|
+
email: string;
|
|
13
|
+
username: string;
|
|
14
|
+
roles: string[];
|
|
15
|
+
sessionId: string;
|
|
16
|
+
type: "access";
|
|
17
|
+
/** Registered claims stamped by `jsonwebtoken` at sign time. */
|
|
18
|
+
iat?: number;
|
|
19
|
+
exp?: number;
|
|
20
|
+
};
|
|
21
|
+
/** What a caller supplies when minting — the registered claims are added for it. */
|
|
22
|
+
export type JwtAccessClaims = Omit<JwtAccessPayload, "iat" | "exp">;
|
|
23
|
+
/** Everything needed to verify a token. Must match the issuing service's values. */
|
|
24
|
+
export type JwtVerifyConfig = {
|
|
25
|
+
secret: string;
|
|
26
|
+
issuer: string;
|
|
27
|
+
audience: string;
|
|
28
|
+
};
|
|
29
|
+
/** Verification config plus the lifetime applied to newly minted tokens. */
|
|
30
|
+
export type JwtSignConfig = JwtVerifyConfig & {
|
|
31
|
+
accessTokenTtlSeconds: number;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/types/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AAEpE,oFAAoF;AACpF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/types/jwt.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@easyweb/authentication",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared authentication primitives for Easyweb microservices: stateless JWT middleware, request context, and HTTP error classes",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"build:watch": "tsc --watch",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"clean": "rimraf dist"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"microservices",
|
|
26
|
+
"express",
|
|
27
|
+
"middleware",
|
|
28
|
+
"authentication",
|
|
29
|
+
"jwt",
|
|
30
|
+
"error-handling"
|
|
31
|
+
],
|
|
32
|
+
"author": "Easy Web Team",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/Alpine-Solusi/website-builder.git",
|
|
37
|
+
"directory": "common-auth"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"express": "^5.0.0",
|
|
41
|
+
"jsonwebtoken": "^9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/express": "^5.0.6",
|
|
45
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
46
|
+
"@types/node": "^26.0.1",
|
|
47
|
+
"rimraf": "^6.1.3",
|
|
48
|
+
"typescript": "^6.0.3"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|