@spinajs/rbac-http-token 2.0.507
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 +152 -0
- package/lib/cjs/actions.d.ts +119 -0
- package/lib/cjs/actions.d.ts.map +1 -0
- package/lib/cjs/actions.js +257 -0
- package/lib/cjs/actions.js.map +1 -0
- package/lib/cjs/cli/CreateToken.d.ts +13 -0
- package/lib/cjs/cli/CreateToken.d.ts.map +1 -0
- package/lib/cjs/cli/CreateToken.js +56 -0
- package/lib/cjs/cli/CreateToken.js.map +1 -0
- package/lib/cjs/cli/DeleteExpiredTokens.d.ts +11 -0
- package/lib/cjs/cli/DeleteExpiredTokens.d.ts.map +1 -0
- package/lib/cjs/cli/DeleteExpiredTokens.js +39 -0
- package/lib/cjs/cli/DeleteExpiredTokens.js.map +1 -0
- package/lib/cjs/cli/DeleteToken.d.ts +7 -0
- package/lib/cjs/cli/DeleteToken.d.ts.map +1 -0
- package/lib/cjs/cli/DeleteToken.js +36 -0
- package/lib/cjs/cli/DeleteToken.js.map +1 -0
- package/lib/cjs/cli/GrantTokenRole.d.ts +7 -0
- package/lib/cjs/cli/GrantTokenRole.d.ts.map +1 -0
- package/lib/cjs/cli/GrantTokenRole.js +37 -0
- package/lib/cjs/cli/GrantTokenRole.js.map +1 -0
- package/lib/cjs/cli/RevokeTokenRole.d.ts +12 -0
- package/lib/cjs/cli/RevokeTokenRole.d.ts.map +1 -0
- package/lib/cjs/cli/RevokeTokenRole.js +42 -0
- package/lib/cjs/cli/RevokeTokenRole.js.map +1 -0
- package/lib/cjs/config/rbac-http-token.d.ts +71 -0
- package/lib/cjs/config/rbac-http-token.d.ts.map +1 -0
- package/lib/cjs/config/rbac-http-token.js +98 -0
- package/lib/cjs/config/rbac-http-token.js.map +1 -0
- package/lib/cjs/controllers/AccessTokenController.d.ts +139 -0
- package/lib/cjs/controllers/AccessTokenController.d.ts.map +1 -0
- package/lib/cjs/controllers/AccessTokenController.js +264 -0
- package/lib/cjs/controllers/AccessTokenController.js.map +1 -0
- package/lib/cjs/dto/create-token-dto.d.ts +34 -0
- package/lib/cjs/dto/create-token-dto.d.ts.map +1 -0
- package/lib/cjs/dto/create-token-dto.js +35 -0
- package/lib/cjs/dto/create-token-dto.js.map +1 -0
- package/lib/cjs/events/AccessTokenCreated.d.ts +4 -0
- package/lib/cjs/events/AccessTokenCreated.d.ts.map +1 -0
- package/lib/cjs/events/AccessTokenCreated.js +18 -0
- package/lib/cjs/events/AccessTokenCreated.js.map +1 -0
- package/lib/cjs/events/AccessTokenDeleted.d.ts +4 -0
- package/lib/cjs/events/AccessTokenDeleted.d.ts.map +1 -0
- package/lib/cjs/events/AccessTokenDeleted.js +18 -0
- package/lib/cjs/events/AccessTokenDeleted.js.map +1 -0
- package/lib/cjs/events/AccessTokenEvent.d.ts +10 -0
- package/lib/cjs/events/AccessTokenEvent.d.ts.map +1 -0
- package/lib/cjs/events/AccessTokenEvent.js +26 -0
- package/lib/cjs/events/AccessTokenEvent.js.map +1 -0
- package/lib/cjs/events/AccessTokenRoleGranted.d.ts +7 -0
- package/lib/cjs/events/AccessTokenRoleGranted.d.ts.map +1 -0
- package/lib/cjs/events/AccessTokenRoleGranted.js +27 -0
- package/lib/cjs/events/AccessTokenRoleGranted.js.map +1 -0
- package/lib/cjs/events/AccessTokenRoleRevoked.d.ts +7 -0
- package/lib/cjs/events/AccessTokenRoleRevoked.d.ts.map +1 -0
- package/lib/cjs/events/AccessTokenRoleRevoked.js +27 -0
- package/lib/cjs/events/AccessTokenRoleRevoked.js.map +1 -0
- package/lib/cjs/events/index.d.ts +6 -0
- package/lib/cjs/events/index.d.ts.map +1 -0
- package/lib/cjs/events/index.js +22 -0
- package/lib/cjs/events/index.js.map +1 -0
- package/lib/cjs/generator.d.ts +12 -0
- package/lib/cjs/generator.d.ts.map +1 -0
- package/lib/cjs/generator.js +42 -0
- package/lib/cjs/generator.js.map +1 -0
- package/lib/cjs/index.d.ts +20 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/lib/cjs/index.js +39 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/interfaces.d.ts +57 -0
- package/lib/cjs/interfaces.d.ts.map +1 -0
- package/lib/cjs/interfaces.js +11 -0
- package/lib/cjs/interfaces.js.map +1 -0
- package/lib/cjs/middlewares.d.ts +76 -0
- package/lib/cjs/middlewares.d.ts.map +1 -0
- package/lib/cjs/middlewares.js +181 -0
- package/lib/cjs/middlewares.js.map +1 -0
- package/lib/cjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts +6 -0
- package/lib/cjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts.map +1 -0
- package/lib/cjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js +38 -0
- package/lib/cjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js.map +1 -0
- package/lib/cjs/models/AccessToken.d.ts +55 -0
- package/lib/cjs/models/AccessToken.d.ts.map +1 -0
- package/lib/cjs/models/AccessToken.js +73 -0
- package/lib/cjs/models/AccessToken.js.map +1 -0
- package/lib/cjs/package.json +1 -0
- package/lib/cjs/policies/NoImpersonationPolicy.d.ts +28 -0
- package/lib/cjs/policies/NoImpersonationPolicy.d.ts.map +1 -0
- package/lib/cjs/policies/NoImpersonationPolicy.js +45 -0
- package/lib/cjs/policies/NoImpersonationPolicy.js.map +1 -0
- package/lib/cjs/policies/NoTokenAuthPolicy.d.ts +12 -0
- package/lib/cjs/policies/NoTokenAuthPolicy.d.ts.map +1 -0
- package/lib/cjs/policies/NoTokenAuthPolicy.js +24 -0
- package/lib/cjs/policies/NoTokenAuthPolicy.js.map +1 -0
- package/lib/cjs/policies/TokenPolicy.d.ts +49 -0
- package/lib/cjs/policies/TokenPolicy.d.ts.map +1 -0
- package/lib/cjs/policies/TokenPolicy.js +79 -0
- package/lib/cjs/policies/TokenPolicy.js.map +1 -0
- package/lib/mjs/actions.d.ts +119 -0
- package/lib/mjs/actions.d.ts.map +1 -0
- package/lib/mjs/actions.js +242 -0
- package/lib/mjs/actions.js.map +1 -0
- package/lib/mjs/cli/CreateToken.d.ts +13 -0
- package/lib/mjs/cli/CreateToken.d.ts.map +1 -0
- package/lib/mjs/cli/CreateToken.js +53 -0
- package/lib/mjs/cli/CreateToken.js.map +1 -0
- package/lib/mjs/cli/DeleteExpiredTokens.d.ts +11 -0
- package/lib/mjs/cli/DeleteExpiredTokens.d.ts.map +1 -0
- package/lib/mjs/cli/DeleteExpiredTokens.js +36 -0
- package/lib/mjs/cli/DeleteExpiredTokens.js.map +1 -0
- package/lib/mjs/cli/DeleteToken.d.ts +7 -0
- package/lib/mjs/cli/DeleteToken.d.ts.map +1 -0
- package/lib/mjs/cli/DeleteToken.js +33 -0
- package/lib/mjs/cli/DeleteToken.js.map +1 -0
- package/lib/mjs/cli/GrantTokenRole.d.ts +7 -0
- package/lib/mjs/cli/GrantTokenRole.d.ts.map +1 -0
- package/lib/mjs/cli/GrantTokenRole.js +34 -0
- package/lib/mjs/cli/GrantTokenRole.js.map +1 -0
- package/lib/mjs/cli/RevokeTokenRole.d.ts +12 -0
- package/lib/mjs/cli/RevokeTokenRole.d.ts.map +1 -0
- package/lib/mjs/cli/RevokeTokenRole.js +39 -0
- package/lib/mjs/cli/RevokeTokenRole.js.map +1 -0
- package/lib/mjs/config/rbac-http-token.d.ts +71 -0
- package/lib/mjs/config/rbac-http-token.d.ts.map +1 -0
- package/lib/mjs/config/rbac-http-token.js +96 -0
- package/lib/mjs/config/rbac-http-token.js.map +1 -0
- package/lib/mjs/controllers/AccessTokenController.d.ts +139 -0
- package/lib/mjs/controllers/AccessTokenController.d.ts.map +1 -0
- package/lib/mjs/controllers/AccessTokenController.js +261 -0
- package/lib/mjs/controllers/AccessTokenController.js.map +1 -0
- package/lib/mjs/dto/create-token-dto.d.ts +34 -0
- package/lib/mjs/dto/create-token-dto.d.ts.map +1 -0
- package/lib/mjs/dto/create-token-dto.js +32 -0
- package/lib/mjs/dto/create-token-dto.js.map +1 -0
- package/lib/mjs/events/AccessTokenCreated.d.ts +4 -0
- package/lib/mjs/events/AccessTokenCreated.d.ts.map +1 -0
- package/lib/mjs/events/AccessTokenCreated.js +15 -0
- package/lib/mjs/events/AccessTokenCreated.js.map +1 -0
- package/lib/mjs/events/AccessTokenDeleted.d.ts +4 -0
- package/lib/mjs/events/AccessTokenDeleted.d.ts.map +1 -0
- package/lib/mjs/events/AccessTokenDeleted.js +15 -0
- package/lib/mjs/events/AccessTokenDeleted.js.map +1 -0
- package/lib/mjs/events/AccessTokenEvent.d.ts +10 -0
- package/lib/mjs/events/AccessTokenEvent.d.ts.map +1 -0
- package/lib/mjs/events/AccessTokenEvent.js +23 -0
- package/lib/mjs/events/AccessTokenEvent.js.map +1 -0
- package/lib/mjs/events/AccessTokenRoleGranted.d.ts +7 -0
- package/lib/mjs/events/AccessTokenRoleGranted.d.ts.map +1 -0
- package/lib/mjs/events/AccessTokenRoleGranted.js +24 -0
- package/lib/mjs/events/AccessTokenRoleGranted.js.map +1 -0
- package/lib/mjs/events/AccessTokenRoleRevoked.d.ts +7 -0
- package/lib/mjs/events/AccessTokenRoleRevoked.d.ts.map +1 -0
- package/lib/mjs/events/AccessTokenRoleRevoked.js +24 -0
- package/lib/mjs/events/AccessTokenRoleRevoked.js.map +1 -0
- package/lib/mjs/events/index.d.ts +6 -0
- package/lib/mjs/events/index.d.ts.map +1 -0
- package/lib/mjs/events/index.js +6 -0
- package/lib/mjs/events/index.js.map +1 -0
- package/lib/mjs/generator.d.ts +12 -0
- package/lib/mjs/generator.d.ts.map +1 -0
- package/lib/mjs/generator.js +39 -0
- package/lib/mjs/generator.js.map +1 -0
- package/lib/mjs/index.d.ts +20 -0
- package/lib/mjs/index.d.ts.map +1 -0
- package/lib/mjs/index.js +23 -0
- package/lib/mjs/index.js.map +1 -0
- package/lib/mjs/interfaces.d.ts +57 -0
- package/lib/mjs/interfaces.d.ts.map +1 -0
- package/lib/mjs/interfaces.js +7 -0
- package/lib/mjs/interfaces.js.map +1 -0
- package/lib/mjs/middlewares.d.ts +76 -0
- package/lib/mjs/middlewares.d.ts.map +1 -0
- package/lib/mjs/middlewares.js +178 -0
- package/lib/mjs/middlewares.js.map +1 -0
- package/lib/mjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts +6 -0
- package/lib/mjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts.map +1 -0
- package/lib/mjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js +35 -0
- package/lib/mjs/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js.map +1 -0
- package/lib/mjs/models/AccessToken.d.ts +55 -0
- package/lib/mjs/models/AccessToken.d.ts.map +1 -0
- package/lib/mjs/models/AccessToken.js +70 -0
- package/lib/mjs/models/AccessToken.js.map +1 -0
- package/lib/mjs/package.json +1 -0
- package/lib/mjs/policies/NoImpersonationPolicy.d.ts +28 -0
- package/lib/mjs/policies/NoImpersonationPolicy.d.ts.map +1 -0
- package/lib/mjs/policies/NoImpersonationPolicy.js +41 -0
- package/lib/mjs/policies/NoImpersonationPolicy.js.map +1 -0
- package/lib/mjs/policies/NoTokenAuthPolicy.d.ts +12 -0
- package/lib/mjs/policies/NoTokenAuthPolicy.d.ts.map +1 -0
- package/lib/mjs/policies/NoTokenAuthPolicy.js +20 -0
- package/lib/mjs/policies/NoTokenAuthPolicy.js.map +1 -0
- package/lib/mjs/policies/TokenPolicy.d.ts +49 -0
- package/lib/mjs/policies/TokenPolicy.d.ts.map +1 -0
- package/lib/mjs/policies/TokenPolicy.js +75 -0
- package/lib/mjs/policies/TokenPolicy.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/tsconfig.mjs.tsbuildinfo +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SecureRandomTokenProvider = void 0;
|
|
13
|
+
const crypto_1 = require("crypto");
|
|
14
|
+
const di_1 = require("@spinajs/di");
|
|
15
|
+
const configuration_1 = require("@spinajs/configuration");
|
|
16
|
+
const interfaces_js_1 = require("./interfaces.js");
|
|
17
|
+
/**
|
|
18
|
+
* Default token algorithm: `<prefix>` + N crypto-random bytes base64url encoded.
|
|
19
|
+
* The stable prefix makes leaked tokens findable by secret scanners.
|
|
20
|
+
*/
|
|
21
|
+
let SecureRandomTokenProvider = class SecureRandomTokenProvider extends interfaces_js_1.AccessTokenGenerationProvider {
|
|
22
|
+
async generate() {
|
|
23
|
+
const plaintext = `${this.Prefix}${(0, crypto_1.randomBytes)(this.Length).toString('base64url')}`;
|
|
24
|
+
return { Plaintext: plaintext, Hash: this.hash(plaintext) };
|
|
25
|
+
}
|
|
26
|
+
hash(plaintext) {
|
|
27
|
+
return (0, crypto_1.createHash)('sha256').update(plaintext).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.SecureRandomTokenProvider = SecureRandomTokenProvider;
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, configuration_1.Config)('rbac.token.prefix', { defaultValue: 'spt_' }),
|
|
33
|
+
__metadata("design:type", String)
|
|
34
|
+
], SecureRandomTokenProvider.prototype, "Prefix", void 0);
|
|
35
|
+
__decorate([
|
|
36
|
+
(0, configuration_1.Config)('rbac.token.length', { defaultValue: 32 }),
|
|
37
|
+
__metadata("design:type", Number)
|
|
38
|
+
], SecureRandomTokenProvider.prototype, "Length", void 0);
|
|
39
|
+
exports.SecureRandomTokenProvider = SecureRandomTokenProvider = __decorate([
|
|
40
|
+
(0, di_1.Injectable)(interfaces_js_1.AccessTokenGenerationProvider)
|
|
41
|
+
], SecureRandomTokenProvider);
|
|
42
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/generator.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAAiD;AACjD,oCAAyC;AACzC,0DAAgD;AAEhD,mDAAiF;AAEjF;;;GAGG;AAEI,IAAM,yBAAyB,GAA/B,MAAM,yBAA0B,SAAQ,6CAA6B;IAOnE,KAAK,CAAC,QAAQ;QACnB,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,IAAA,oBAAW,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACpF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;IAC9D,CAAC;IAEM,IAAI,CAAC,SAAiB;QAC3B,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;CACF,CAAA;;AAbW;IADT,IAAA,sBAAM,EAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;;yDAC7B;AAGf;IADT,IAAA,sBAAM,EAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;;yDACzB;oCALd,yBAAyB;IADrC,IAAA,eAAU,EAAC,6CAA6B,CAAC;GAC7B,yBAAyB,CAerC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import './generator.js';
|
|
2
|
+
import './middlewares.js';
|
|
3
|
+
export * from './interfaces.js';
|
|
4
|
+
export * from './models/AccessToken.js';
|
|
5
|
+
export * from './migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js';
|
|
6
|
+
export * from './generator.js';
|
|
7
|
+
export * from './events/index.js';
|
|
8
|
+
export * from './actions.js';
|
|
9
|
+
export * from './middlewares.js';
|
|
10
|
+
export * from './policies/TokenPolicy.js';
|
|
11
|
+
export * from './policies/NoTokenAuthPolicy.js';
|
|
12
|
+
export * from './policies/NoImpersonationPolicy.js';
|
|
13
|
+
export * from './controllers/AccessTokenController.js';
|
|
14
|
+
export * from './dto/create-token-dto.js';
|
|
15
|
+
export * from './cli/CreateToken.js';
|
|
16
|
+
export * from './cli/DeleteToken.js';
|
|
17
|
+
export * from './cli/GrantTokenRole.js';
|
|
18
|
+
export * from './cli/RevokeTokenRole.js';
|
|
19
|
+
export * from './cli/DeleteExpiredTokens.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,gBAAgB,CAAC;AACxB,OAAO,kBAAkB,CAAC;AAE1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0DAA0D,CAAC;AACzE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AACvD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC"}
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
// Side effect imports - these modules register Injectables ( the token
|
|
18
|
+
// generator and the token authorization middleware ) the moment they are
|
|
19
|
+
// loaded, so they must run even if a consumer imports nothing from them.
|
|
20
|
+
require("./generator.js");
|
|
21
|
+
require("./middlewares.js");
|
|
22
|
+
__exportStar(require("./interfaces.js"), exports);
|
|
23
|
+
__exportStar(require("./models/AccessToken.js"), exports);
|
|
24
|
+
__exportStar(require("./migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.js"), exports);
|
|
25
|
+
__exportStar(require("./generator.js"), exports);
|
|
26
|
+
__exportStar(require("./events/index.js"), exports);
|
|
27
|
+
__exportStar(require("./actions.js"), exports);
|
|
28
|
+
__exportStar(require("./middlewares.js"), exports);
|
|
29
|
+
__exportStar(require("./policies/TokenPolicy.js"), exports);
|
|
30
|
+
__exportStar(require("./policies/NoTokenAuthPolicy.js"), exports);
|
|
31
|
+
__exportStar(require("./policies/NoImpersonationPolicy.js"), exports);
|
|
32
|
+
__exportStar(require("./controllers/AccessTokenController.js"), exports);
|
|
33
|
+
__exportStar(require("./dto/create-token-dto.js"), exports);
|
|
34
|
+
__exportStar(require("./cli/CreateToken.js"), exports);
|
|
35
|
+
__exportStar(require("./cli/DeleteToken.js"), exports);
|
|
36
|
+
__exportStar(require("./cli/GrantTokenRole.js"), exports);
|
|
37
|
+
__exportStar(require("./cli/RevokeTokenRole.js"), exports);
|
|
38
|
+
__exportStar(require("./cli/DeleteExpiredTokens.js"), exports);
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,0BAAwB;AACxB,4BAA0B;AAE1B,kDAAgC;AAChC,0DAAwC;AACxC,2FAAyE;AACzE,iDAA+B;AAC/B,oDAAkC;AAClC,+CAA6B;AAC7B,mDAAiC;AACjC,4DAA0C;AAC1C,kEAAgD;AAChD,sEAAoD;AACpD,yEAAuD;AACvD,4DAA0C;AAC1C,uDAAqC;AACrC,uDAAqC;AACrC,0DAAwC;AACxC,2DAAyC;AACzC,+DAA6C"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface IGeneratedToken {
|
|
2
|
+
/**
|
|
3
|
+
* Full token as handed to the user, e.g. `spt_<base64url>`. Shown exactly once.
|
|
4
|
+
*/
|
|
5
|
+
Plaintext: string;
|
|
6
|
+
/**
|
|
7
|
+
* SHA-256 hex digest of the plaintext - the only thing that is persisted.
|
|
8
|
+
*/
|
|
9
|
+
Hash: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Token generation algorithm. Replaceable via config
|
|
13
|
+
* `rbac.token.generation.service` - same pattern as `rbac.password.service`.
|
|
14
|
+
*/
|
|
15
|
+
export declare abstract class AccessTokenGenerationProvider {
|
|
16
|
+
/**
|
|
17
|
+
* Generates a fresh token. Plaintext leaves the server once; only the hash is stored.
|
|
18
|
+
*/
|
|
19
|
+
abstract generate(): Promise<IGeneratedToken>;
|
|
20
|
+
/**
|
|
21
|
+
* Deterministic hash of a presented plaintext, used for DB lookup.
|
|
22
|
+
*/
|
|
23
|
+
abstract hash(plaintext: string): string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Marker stored on request async storage when a request was authenticated
|
|
27
|
+
* with an access token instead of a session.
|
|
28
|
+
*/
|
|
29
|
+
export interface ITokenAuthInfo {
|
|
30
|
+
/**
|
|
31
|
+
* Uuid of the AccessToken row - safe to log; never the token itself.
|
|
32
|
+
*/
|
|
33
|
+
Uuid: string;
|
|
34
|
+
}
|
|
35
|
+
declare module '@spinajs/rbac' {
|
|
36
|
+
interface IRbacAsyncStorage {
|
|
37
|
+
/**
|
|
38
|
+
* Set by TokenAuthMiddleware when the request carries a valid access token.
|
|
39
|
+
*/
|
|
40
|
+
TokenAuth?: ITokenAuthInfo;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The http request-local storage is a SEPARATE interface from rbac's
|
|
45
|
+
* `IRbacAsyncStorage` ( `@spinajs/rbac-http` augments it the same way with
|
|
46
|
+
* User / Session / ActiveRole ), so the marker has to be declared on both -
|
|
47
|
+
* the one above for rbac's execution context, this one for `req.storage`.
|
|
48
|
+
*/
|
|
49
|
+
declare module '@spinajs/http' {
|
|
50
|
+
interface IActionLocalStoregeContext {
|
|
51
|
+
/**
|
|
52
|
+
* Set by TokenAuthMiddleware when the request carries a valid access token.
|
|
53
|
+
*/
|
|
54
|
+
TokenAuth?: ITokenAuthInfo;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,8BAAsB,6BAA6B;IACjD;;OAEG;IACH,SAAgB,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IAErD;;OAEG;IACH,SAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,iBAAiB;QACzB;;WAEG;QACH,SAAS,CAAC,EAAE,cAAc,CAAC;KAC5B;CACF;AAED;;;;;GAKG;AACH,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,0BAA0B;QAClC;;WAEG;QACH,SAAS,CAAC,EAAE,cAAc,CAAC;KAC5B;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccessTokenGenerationProvider = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Token generation algorithm. Replaceable via config
|
|
6
|
+
* `rbac.token.generation.service` - same pattern as `rbac.password.service`.
|
|
7
|
+
*/
|
|
8
|
+
class AccessTokenGenerationProvider {
|
|
9
|
+
}
|
|
10
|
+
exports.AccessTokenGenerationProvider = AccessTokenGenerationProvider;
|
|
11
|
+
//# sourceMappingURL=interfaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":";;;AAsBA;;;GAGG;AACH;CAUC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as express from 'express';
|
|
2
|
+
import { Log } from '@spinajs/log';
|
|
3
|
+
import { Request as sRequest, ServerMiddleware } from '@spinajs/http';
|
|
4
|
+
import { User } from '@spinajs/rbac';
|
|
5
|
+
import './interfaces.js';
|
|
6
|
+
/**
|
|
7
|
+
* Authenticates requests carrying an access token in `Authorization: Bearer`
|
|
8
|
+
* or the configured fallback header. Runs AFTER RbacMiddleware ( Order 0 )
|
|
9
|
+
* so a session, when present, always wins - a request cannot mix both.
|
|
10
|
+
*
|
|
11
|
+
* On success `req.storage.User` carries the owner with Role narrowed to the
|
|
12
|
+
* token's effective roles, so every downstream permission check ( RbacPolicy
|
|
13
|
+
* helpers, orm rbac query middleware, ownership ) works unchanged.
|
|
14
|
+
*
|
|
15
|
+
* `req.storage.ActiveRole` is deliberately CLEARED, and that is load bearing.
|
|
16
|
+
* Both consumers of the field resolve the roles to authorize with as
|
|
17
|
+
* `ActiveRole ? [ActiveRole] : User.Role` ( `checkRoutePermission` in
|
|
18
|
+
* `rbac-http/src/policies/RbacPolicy.ts`, and the orm rbac query middleware in
|
|
19
|
+
* `rbac/src/middleware.ts` ), so whatever single role sits there collapses the
|
|
20
|
+
* request down to that one role.
|
|
21
|
+
*
|
|
22
|
+
* A session can afford that collapse because it can switch its active role at
|
|
23
|
+
* runtime ( `POST /auth/active-role` ), so the narrowing is both deliberate and
|
|
24
|
+
* reversible. A token carries no session and has no way to switch, so picking
|
|
25
|
+
* one of its roles - the first, arbitrarily - would permanently authorize the
|
|
26
|
+
* token with a strict subset of what it was issued for: a token holding
|
|
27
|
+
* `[ 'user', 'admin' ]` would never satisfy an admin-only grant.
|
|
28
|
+
*
|
|
29
|
+
* Leaving it undefined makes both consumers fall back to `User.Role`, which the
|
|
30
|
+
* narrowing above has ALREADY reduced to exactly the effective role set - the
|
|
31
|
+
* intersection of what the token carries and its owner still holds. That is the
|
|
32
|
+
* correct authorization scope for a token, and nothing wider.
|
|
33
|
+
*
|
|
34
|
+
* The field has to be assigned `undefined` rather than skipped: `RbacMiddleware`
|
|
35
|
+
* runs first and, on a session-less request, stamps the GUEST account's first
|
|
36
|
+
* role into it, which would otherwise survive into the token's own request.
|
|
37
|
+
*
|
|
38
|
+
* Never throws: an invalid token leaves the request as guest and lets the
|
|
39
|
+
* route's policy produce the rejection.
|
|
40
|
+
*/
|
|
41
|
+
export declare class TokenAuthMiddleware extends ServerMiddleware {
|
|
42
|
+
protected Log: Log;
|
|
43
|
+
protected HeaderName: string;
|
|
44
|
+
protected LastUsedUpdateInterval: number;
|
|
45
|
+
constructor();
|
|
46
|
+
before(): (req: sRequest, res: express.Response, next: express.NextFunction) => void;
|
|
47
|
+
after(): null;
|
|
48
|
+
/**
|
|
49
|
+
* Narrows the loaded owner to the token's effective roles WITHOUT letting the
|
|
50
|
+
* narrowing reach the database.
|
|
51
|
+
*
|
|
52
|
+
* `Role` is a real `@Set()` column, and this very instance is what controllers
|
|
53
|
+
* receive through the `@User()` route argument. `update()` writes whatever
|
|
54
|
+
* differs from the model's snapshot, so a controller doing an unrelated edit
|
|
55
|
+
* ( "change my login" ) and calling `.update()` would silently persist the
|
|
56
|
+
* narrowed list - permanently stripping every role the token did not carry.
|
|
57
|
+
*
|
|
58
|
+
* Re-taking the snapshot right after the narrowing makes it invisible to that
|
|
59
|
+
* diff: the instance then behaves exactly like a freshly loaded row whose roles
|
|
60
|
+
* happen to be the effective ones, and a later `.update()` writes only what the
|
|
61
|
+
* controller itself changed. The full instance is kept rather than copied so
|
|
62
|
+
* that `Id`, `Password`, the `Metadata` relation and the prototype accessors
|
|
63
|
+
* ( `IsBanned`, `can()` ) all survive - `dehydrate()` drops the `@Hidden()`
|
|
64
|
+
* columns, so a dehydrate/rehydrate copy would arrive without `Id`.
|
|
65
|
+
*
|
|
66
|
+
* `takeSnapshot()` also discards the relation baselines, which the orm's
|
|
67
|
+
* subject builder needs to tell an already-persisted related row from a new
|
|
68
|
+
* one, so they are recorded again immediately.
|
|
69
|
+
*/
|
|
70
|
+
protected narrowRoles(user: User, roles: string[]): User;
|
|
71
|
+
/**
|
|
72
|
+
* Bearer scheme first, configured fallback header second.
|
|
73
|
+
*/
|
|
74
|
+
protected extract(req: sRequest): string | null;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=middlewares.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middlewares.d.ts","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAGnC,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGrC,OAAO,iBAAiB,CAAC;AAQzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBACa,mBAAoB,SAAQ,gBAAgB;IAEvD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAG7B,SAAS,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAEzC,cAIC;IAEM,MAAM,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,YAAY,KAAK,IAAI,CAkD1F;IAEM,KAAK,IAAI,IAAI,CAEnB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CASvD;IAED;;OAEG;IACH,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAgB9C;CACF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TokenAuthMiddleware = void 0;
|
|
13
|
+
const di_1 = require("@spinajs/di");
|
|
14
|
+
const configuration_1 = require("@spinajs/configuration");
|
|
15
|
+
const log_1 = require("@spinajs/log");
|
|
16
|
+
const http_1 = require("@spinajs/http");
|
|
17
|
+
const exceptions_1 = require("@spinajs/exceptions");
|
|
18
|
+
const actions_js_1 = require("./actions.js");
|
|
19
|
+
require("./interfaces.js");
|
|
20
|
+
/**
|
|
21
|
+
* `Authorization: Bearer <token>`. RFC 7235 makes the scheme name
|
|
22
|
+
* case-insensitive, and clients do send `bearer`.
|
|
23
|
+
*/
|
|
24
|
+
const BEARER_SCHEME = /^bearer\s+/i;
|
|
25
|
+
/**
|
|
26
|
+
* Authenticates requests carrying an access token in `Authorization: Bearer`
|
|
27
|
+
* or the configured fallback header. Runs AFTER RbacMiddleware ( Order 0 )
|
|
28
|
+
* so a session, when present, always wins - a request cannot mix both.
|
|
29
|
+
*
|
|
30
|
+
* On success `req.storage.User` carries the owner with Role narrowed to the
|
|
31
|
+
* token's effective roles, so every downstream permission check ( RbacPolicy
|
|
32
|
+
* helpers, orm rbac query middleware, ownership ) works unchanged.
|
|
33
|
+
*
|
|
34
|
+
* `req.storage.ActiveRole` is deliberately CLEARED, and that is load bearing.
|
|
35
|
+
* Both consumers of the field resolve the roles to authorize with as
|
|
36
|
+
* `ActiveRole ? [ActiveRole] : User.Role` ( `checkRoutePermission` in
|
|
37
|
+
* `rbac-http/src/policies/RbacPolicy.ts`, and the orm rbac query middleware in
|
|
38
|
+
* `rbac/src/middleware.ts` ), so whatever single role sits there collapses the
|
|
39
|
+
* request down to that one role.
|
|
40
|
+
*
|
|
41
|
+
* A session can afford that collapse because it can switch its active role at
|
|
42
|
+
* runtime ( `POST /auth/active-role` ), so the narrowing is both deliberate and
|
|
43
|
+
* reversible. A token carries no session and has no way to switch, so picking
|
|
44
|
+
* one of its roles - the first, arbitrarily - would permanently authorize the
|
|
45
|
+
* token with a strict subset of what it was issued for: a token holding
|
|
46
|
+
* `[ 'user', 'admin' ]` would never satisfy an admin-only grant.
|
|
47
|
+
*
|
|
48
|
+
* Leaving it undefined makes both consumers fall back to `User.Role`, which the
|
|
49
|
+
* narrowing above has ALREADY reduced to exactly the effective role set - the
|
|
50
|
+
* intersection of what the token carries and its owner still holds. That is the
|
|
51
|
+
* correct authorization scope for a token, and nothing wider.
|
|
52
|
+
*
|
|
53
|
+
* The field has to be assigned `undefined` rather than skipped: `RbacMiddleware`
|
|
54
|
+
* runs first and, on a session-less request, stamps the GUEST account's first
|
|
55
|
+
* role into it, which would otherwise survive into the token's own request.
|
|
56
|
+
*
|
|
57
|
+
* Never throws: an invalid token leaves the request as guest and lets the
|
|
58
|
+
* route's policy produce the rejection.
|
|
59
|
+
*/
|
|
60
|
+
let TokenAuthMiddleware = class TokenAuthMiddleware extends http_1.ServerMiddleware {
|
|
61
|
+
constructor() {
|
|
62
|
+
super();
|
|
63
|
+
// After RbacMiddleware ( Order 0 ): session auth takes precedence.
|
|
64
|
+
this.Order = 1;
|
|
65
|
+
}
|
|
66
|
+
before() {
|
|
67
|
+
return async (req, res, next) => {
|
|
68
|
+
try {
|
|
69
|
+
// Session user already authenticated - tokens do not apply.
|
|
70
|
+
if (req.storage?.Session) {
|
|
71
|
+
return next();
|
|
72
|
+
}
|
|
73
|
+
const plaintext = this.extract(req);
|
|
74
|
+
if (!plaintext) {
|
|
75
|
+
return next();
|
|
76
|
+
}
|
|
77
|
+
let result;
|
|
78
|
+
try {
|
|
79
|
+
result = await (0, actions_js_1.validateToken)(plaintext);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
// Deliberately vague towards the client; specific in the log. The uuid
|
|
83
|
+
// is what makes a rejection actionable ( "which key broke" ) and is
|
|
84
|
+
// safe to write down - `validateToken` attaches it as `data.token` on
|
|
85
|
+
// every failure past the lookup. The presented plaintext is NEVER
|
|
86
|
+
// logged, here or anywhere else.
|
|
87
|
+
const data = err instanceof exceptions_1.ErrorCode ? err.data : undefined;
|
|
88
|
+
this.Log.warn(`Access token rejected: ${err.message}`, { Ip: req.ip, Token: data?.token });
|
|
89
|
+
return next();
|
|
90
|
+
}
|
|
91
|
+
req.storage.User = this.narrowRoles(result.User, result.EffectiveRoles);
|
|
92
|
+
req.storage.TokenAuth = { Uuid: result.Token.Uuid };
|
|
93
|
+
// CLEARED, not merely left alone - see the class docblock for why a
|
|
94
|
+
// token must carry no active role at all. `RbacMiddleware` has already
|
|
95
|
+
// run by now and, finding no session, wrote the GUEST user's first role
|
|
96
|
+
// here ( `rbac-http/src/middlewares.ts`, the `else` branch ). Leaving
|
|
97
|
+
// that in place would authorize every token request as `guest`.
|
|
98
|
+
req.storage.ActiveRole = undefined;
|
|
99
|
+
// Token-authenticated responses must never land in a shared cache.
|
|
100
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
101
|
+
// Fire-and-forget throttled usage stamp. A non-Error rejection has no
|
|
102
|
+
// `.message`; reading one off it would throw inside the handler and
|
|
103
|
+
// surface as an unhandled rejection.
|
|
104
|
+
void (0, actions_js_1.touchToken)(result.Token, this.LastUsedUpdateInterval).catch((err) => this.Log.warn(`Failed to update token LastUsedAt: ${err instanceof Error ? err.message : String(err)}`, { Token: result.Token.Uuid }));
|
|
105
|
+
next();
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
next(err);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
after() {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Narrows the loaded owner to the token's effective roles WITHOUT letting the
|
|
117
|
+
* narrowing reach the database.
|
|
118
|
+
*
|
|
119
|
+
* `Role` is a real `@Set()` column, and this very instance is what controllers
|
|
120
|
+
* receive through the `@User()` route argument. `update()` writes whatever
|
|
121
|
+
* differs from the model's snapshot, so a controller doing an unrelated edit
|
|
122
|
+
* ( "change my login" ) and calling `.update()` would silently persist the
|
|
123
|
+
* narrowed list - permanently stripping every role the token did not carry.
|
|
124
|
+
*
|
|
125
|
+
* Re-taking the snapshot right after the narrowing makes it invisible to that
|
|
126
|
+
* diff: the instance then behaves exactly like a freshly loaded row whose roles
|
|
127
|
+
* happen to be the effective ones, and a later `.update()` writes only what the
|
|
128
|
+
* controller itself changed. The full instance is kept rather than copied so
|
|
129
|
+
* that `Id`, `Password`, the `Metadata` relation and the prototype accessors
|
|
130
|
+
* ( `IsBanned`, `can()` ) all survive - `dehydrate()` drops the `@Hidden()`
|
|
131
|
+
* columns, so a dehydrate/rehydrate copy would arrive without `Id`.
|
|
132
|
+
*
|
|
133
|
+
* `takeSnapshot()` also discards the relation baselines, which the orm's
|
|
134
|
+
* subject builder needs to tell an already-persisted related row from a new
|
|
135
|
+
* one, so they are recorded again immediately.
|
|
136
|
+
*/
|
|
137
|
+
narrowRoles(user, roles) {
|
|
138
|
+
user.Role = roles;
|
|
139
|
+
user.takeSnapshot();
|
|
140
|
+
for (const [name] of user.ModelDescriptor?.Relations ?? []) {
|
|
141
|
+
user.snapshotRelation(name);
|
|
142
|
+
}
|
|
143
|
+
return user;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Bearer scheme first, configured fallback header second.
|
|
147
|
+
*/
|
|
148
|
+
extract(req) {
|
|
149
|
+
// A header sent twice arrives as an array. Anything that is not a single
|
|
150
|
+
// string is malformed and has to read as "no token presented" - calling
|
|
151
|
+
// `.startsWith` / `.trim` on it would throw, and this middleware is
|
|
152
|
+
// contractually not allowed to turn a bad request into a 500.
|
|
153
|
+
const header = (name) => {
|
|
154
|
+
const value = req.headers?.[name];
|
|
155
|
+
return typeof value === 'string' ? value : null;
|
|
156
|
+
};
|
|
157
|
+
const auth = header('authorization');
|
|
158
|
+
if (auth && BEARER_SCHEME.test(auth)) {
|
|
159
|
+
return auth.replace(BEARER_SCHEME, '').trim() || null;
|
|
160
|
+
}
|
|
161
|
+
return header(this.HeaderName.toLowerCase())?.trim() || null;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
exports.TokenAuthMiddleware = TokenAuthMiddleware;
|
|
165
|
+
__decorate([
|
|
166
|
+
(0, log_1.Logger)('rbac-http-token'),
|
|
167
|
+
__metadata("design:type", log_1.Log)
|
|
168
|
+
], TokenAuthMiddleware.prototype, "Log", void 0);
|
|
169
|
+
__decorate([
|
|
170
|
+
(0, configuration_1.Config)('rbac.token.headerName', { defaultValue: 'x-api-key' }),
|
|
171
|
+
__metadata("design:type", String)
|
|
172
|
+
], TokenAuthMiddleware.prototype, "HeaderName", void 0);
|
|
173
|
+
__decorate([
|
|
174
|
+
(0, configuration_1.Config)('rbac.token.lastUsedUpdateInterval', { defaultValue: 60 }),
|
|
175
|
+
__metadata("design:type", Number)
|
|
176
|
+
], TokenAuthMiddleware.prototype, "LastUsedUpdateInterval", void 0);
|
|
177
|
+
exports.TokenAuthMiddleware = TokenAuthMiddleware = __decorate([
|
|
178
|
+
(0, di_1.Injectable)(http_1.ServerMiddleware),
|
|
179
|
+
__metadata("design:paramtypes", [])
|
|
180
|
+
], TokenAuthMiddleware);
|
|
181
|
+
//# sourceMappingURL=middlewares.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middlewares.js","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { OrmMigration, OrmDriver } from '@spinajs/orm';
|
|
2
|
+
export declare class RbacHttpTokenInitial_2026_08_11_01_00_00 extends OrmMigration {
|
|
3
|
+
up(connection: OrmDriver): Promise<void>;
|
|
4
|
+
down(_connection: OrmDriver): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RbacHttpTokenInitial_2026_08_11_01_00_00.d.ts","sourceRoot":"","sources":["../../../src/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAa,MAAM,cAAc,CAAC;AAElE,qBACa,wCAAyC,SAAQ,YAAY;IAC3D,EAAE,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpD;IAGY,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAG;CAC5D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.RbacHttpTokenInitial_2026_08_11_01_00_00 = void 0;
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
11
|
+
const orm_1 = require("@spinajs/orm");
|
|
12
|
+
let RbacHttpTokenInitial_2026_08_11_01_00_00 = class RbacHttpTokenInitial_2026_08_11_01_00_00 extends orm_1.OrmMigration {
|
|
13
|
+
async up(connection) {
|
|
14
|
+
await connection.schema().createTable('rbac_access_tokens', (table) => {
|
|
15
|
+
table.int('Id').primaryKey().autoIncrement();
|
|
16
|
+
table.string('Uuid', 36).notNull();
|
|
17
|
+
table.string('Name', 128).notNull();
|
|
18
|
+
table.string('Token', 64).notNull();
|
|
19
|
+
table.string('Roles', 512).notNull();
|
|
20
|
+
table.dateTime('ExpiresAt');
|
|
21
|
+
table.dateTime('CreatedAt').notNull().default().dateTime();
|
|
22
|
+
table.dateTime('LastUsedAt');
|
|
23
|
+
table.int('user_id').notNull();
|
|
24
|
+
table.foreignKey('user_id').references('users', 'Id').cascade();
|
|
25
|
+
});
|
|
26
|
+
await connection.index().unique().table('rbac_access_tokens').name('access_token_hash_idx').columns(['Token']);
|
|
27
|
+
await connection.index().unique().table('rbac_access_tokens').name('access_token_uuid_idx').columns(['Uuid']);
|
|
28
|
+
await connection.index().table('rbac_access_tokens').name('access_token_user_idx').columns(['user_id']);
|
|
29
|
+
await connection.index().table('rbac_access_tokens').name('access_token_expires_idx').columns(['ExpiresAt']);
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
32
|
+
async down(_connection) { }
|
|
33
|
+
};
|
|
34
|
+
exports.RbacHttpTokenInitial_2026_08_11_01_00_00 = RbacHttpTokenInitial_2026_08_11_01_00_00;
|
|
35
|
+
exports.RbacHttpTokenInitial_2026_08_11_01_00_00 = RbacHttpTokenInitial_2026_08_11_01_00_00 = __decorate([
|
|
36
|
+
(0, orm_1.Migration)('default')
|
|
37
|
+
], RbacHttpTokenInitial_2026_08_11_01_00_00);
|
|
38
|
+
//# sourceMappingURL=RbacHttpTokenInitial_2026_08_11_01_00_00.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RbacHttpTokenInitial_2026_08_11_01_00_00.js","sourceRoot":"","sources":["../../../src/migrations/RbacHttpTokenInitial_2026_08_11_01_00_00.ts"],"names":[],"mappings":";;;;;;;;;AAAA,sDAAsD;AACtD,sCAAkE;AAG3D,IAAM,wCAAwC,GAA9C,MAAM,wCAAyC,SAAQ,kBAAY;IACjE,KAAK,CAAC,EAAE,CAAC,UAAqB;QACnC,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE;YACpE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE,CAAC;YAC7C,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACnC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;YACpC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACpC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;YACrC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC5B,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC3D,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC7B,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;YAC/B,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/G,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9G,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACxG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/G,CAAC;IAED,gEAAgE;IACzD,KAAK,CAAC,IAAI,CAAC,WAAsB,IAAkB,CAAC;CAC5D,CAAA;;mDAvBY,wCAAwC;IADpD,IAAA,eAAS,EAAC,SAAS,CAAC;GACR,wCAAwC,CAuBpD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ModelBase, SingleRelation } from '@spinajs/orm';
|
|
2
|
+
import { User } from '@spinajs/rbac';
|
|
3
|
+
import { DateTime } from 'luxon';
|
|
4
|
+
/**
|
|
5
|
+
* Personal access token. Only the SHA-256 hash of the token is stored;
|
|
6
|
+
* the plaintext is returned once at creation and cannot be recovered.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AccessToken extends ModelBase<AccessToken> {
|
|
9
|
+
constructor(data?: Partial<AccessToken>);
|
|
10
|
+
/**
|
|
11
|
+
* Internal row id, never leaves the process. Tokens are addressed by Uuid.
|
|
12
|
+
*/
|
|
13
|
+
Id: number;
|
|
14
|
+
/**
|
|
15
|
+
* Public identifier used by the API and CLI.
|
|
16
|
+
*/
|
|
17
|
+
Uuid: string;
|
|
18
|
+
/**
|
|
19
|
+
* Human readable label ("ci deploy key").
|
|
20
|
+
*/
|
|
21
|
+
Name: string;
|
|
22
|
+
/**
|
|
23
|
+
* SHA-256 hex digest of the plaintext token. Hidden: even the hash is
|
|
24
|
+
* internal - leaking it invites offline correlation.
|
|
25
|
+
*/
|
|
26
|
+
Token: string;
|
|
27
|
+
/**
|
|
28
|
+
* Roles allowed on this token. Effective roles at request time are the
|
|
29
|
+
* intersection of this list with the owner's current roles.
|
|
30
|
+
*/
|
|
31
|
+
Roles: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Absolute expiration. Null/absent = never expires.
|
|
34
|
+
*
|
|
35
|
+
* NOTE: declared optional rather than as a `DateTime | null` union - the
|
|
36
|
+
* `@DateTime()` decorator reads `design:type`, and a union is emitted as
|
|
37
|
+
* `Object`, which it rejects at decoration time. An optional property still
|
|
38
|
+
* emits `design:type = DateTime` while keeping the absence in the type.
|
|
39
|
+
*/
|
|
40
|
+
ExpiresAt?: DateTime;
|
|
41
|
+
CreatedAt: DateTime;
|
|
42
|
+
/**
|
|
43
|
+
* Last successful authentication with this token. Updated throttled.
|
|
44
|
+
* Null/absent until the token is used for the first time - see the note on
|
|
45
|
+
* `ExpiresAt` for why this is optional rather than a nullable union.
|
|
46
|
+
*/
|
|
47
|
+
LastUsedAt?: DateTime;
|
|
48
|
+
User: SingleRelation<User>;
|
|
49
|
+
user_id: number;
|
|
50
|
+
/**
|
|
51
|
+
* True when the token carries an expiration in the past.
|
|
52
|
+
*/
|
|
53
|
+
get IsExpired(): boolean;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=AccessToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccessToken.d.ts","sourceRoot":"","sources":["../../../src/models/AccessToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmE,SAAS,EAAgB,cAAc,EAAE,MAAM,cAAc,CAAC;AACxI,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAIjC;;;GAGG;AACH,qBAEa,WAAY,SAAQ,SAAS,CAAC,WAAW,CAAC;IACrD,YAAmB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,EAG7C;IAED;;OAEG;IAGI,EAAE,EAAG,MAAM,CAAC;IAEnB;;OAEG;IACI,IAAI,EAAG,MAAM,CAAC;IAErB;;OAEG;IACI,IAAI,EAAG,MAAM,CAAC;IAErB;;;OAGG;IAEI,KAAK,EAAG,MAAM,CAAC;IAEtB;;;OAGG;IAEI,KAAK,EAAG,MAAM,EAAE,CAAC;IAExB;;;;;;;OAOG;IAEI,SAAS,CAAC,EAAE,QAAQ,CAAC;IAGrB,SAAS,EAAG,QAAQ,CAAC;IAE5B;;;;OAIG;IAEI,UAAU,CAAC,EAAE,QAAQ,CAAC;IAItB,IAAI,EAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAG5B,OAAO,EAAG,MAAM,CAAC;IAExB;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;CACF"}
|