@nano-app/backend 0.6.4 → 0.7.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/LICENSE +21 -0
- package/README.md +233 -0
- package/build/index.d.ts +149 -0
- package/build/index.js +544 -0
- package/build/nano-app.js +41 -0
- package/package.json +70 -26
- package/dist/app.d.ts +0 -9
- package/dist/app.js +0 -74
- package/dist/auth/ApiKeys.d.ts +0 -25
- package/dist/auth/ApiKeys.js +0 -146
- package/dist/auth/ApiKeysEndpoint.d.ts +0 -19
- package/dist/auth/ApiKeysEndpoint.js +0 -36
- package/dist/auth/ClientCredentials.d.ts +0 -15
- package/dist/auth/ClientCredentials.js +0 -132
- package/dist/auth/TokenEndpoint.d.ts +0 -5
- package/dist/auth/TokenEndpoint.js +0 -33
- package/dist/http/Endpoint.d.ts +0 -17
- package/dist/http/Endpoint.js +0 -59
- package/dist/http/Router.d.ts +0 -6
- package/dist/http/Router.js +0 -20
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -28
- package/dist/odata/Entities.d.ts +0 -10
- package/dist/odata/Entities.js +0 -52
- package/dist/odata/QueryBuilder.d.ts +0 -10
- package/dist/odata/QueryBuilder.js +0 -60
- package/dist/prisma/Client.d.ts +0 -12
- package/dist/prisma/Client.js +0 -34
- package/dist/types/api.d.ts +0 -6
- package/dist/types/api.js +0 -2
- package/dist/types/app.d.ts +0 -29
- package/dist/types/app.js +0 -2
- package/dist/types/odata.d.ts +0 -8
- package/dist/types/odata.js +0 -2
- package/dist/utils/Formatter.d.ts +0 -6
- package/dist/utils/Formatter.js +0 -31
package/package.json
CHANGED
|
@@ -1,46 +1,90 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nano-app/backend",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Express server with file-convention routes, plugins and an optional PostgreSQL layer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./build/index.d.ts",
|
|
11
|
+
"import": "./build/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"files": [
|
|
8
|
-
"
|
|
15
|
+
"build",
|
|
16
|
+
"LICENSE"
|
|
9
17
|
],
|
|
10
18
|
"scripts": {
|
|
11
|
-
"build": "
|
|
12
|
-
"
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"test": "tsx --test tests/*.test.ts",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
13
23
|
},
|
|
14
24
|
"keywords": [
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
25
|
+
"express",
|
|
26
|
+
"backend",
|
|
27
|
+
"plugins",
|
|
28
|
+
"file-based-routing",
|
|
29
|
+
"postgresql",
|
|
30
|
+
"prisma"
|
|
19
31
|
],
|
|
20
32
|
"author": "Cristian Serrano",
|
|
21
33
|
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Nano-Nathan/nano-app.git",
|
|
37
|
+
"directory": "backend"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
22
45
|
"dependencies": {
|
|
23
|
-
"@prisma/adapter-pg": "^7.4.0",
|
|
24
|
-
"@prisma/client": "^7.4.0",
|
|
25
|
-
"bcrypt": "^6.0.0",
|
|
26
46
|
"compression": "^1.8.1",
|
|
27
|
-
"cors": "^2.8.
|
|
28
|
-
"dotenv": "^17.2
|
|
47
|
+
"cors": "^2.8.6",
|
|
48
|
+
"dotenv": "^17.4.2",
|
|
29
49
|
"express": "^5.2.1",
|
|
30
|
-
"express-rate-limit": "^8.2
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
50
|
+
"express-rate-limit": "^8.5.2",
|
|
51
|
+
"glob": "^13.0.6",
|
|
52
|
+
"helmet": "^8.2.0",
|
|
53
|
+
"jiti": "^2.7.0",
|
|
54
|
+
"morgan": "^1.11.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
58
|
+
"@prisma/client": "^7.8.0",
|
|
59
|
+
"pg": "^8.22.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@prisma/adapter-pg": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@prisma/client": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"pg": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
34
71
|
},
|
|
35
72
|
"devDependencies": {
|
|
36
|
-
"@
|
|
73
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
74
|
+
"@prisma/client": "^7.8.0",
|
|
37
75
|
"@types/compression": "^1.8.1",
|
|
38
76
|
"@types/cors": "^2.8.19",
|
|
39
|
-
"@types/express": "^5.0.
|
|
77
|
+
"@types/express": "^5.0.6",
|
|
40
78
|
"@types/morgan": "^1.9.10",
|
|
41
|
-
"@types/node": "^
|
|
42
|
-
"@types/pg": "^8.
|
|
43
|
-
"
|
|
44
|
-
"
|
|
79
|
+
"@types/node": "^22.19.0",
|
|
80
|
+
"@types/pg": "^8.20.0",
|
|
81
|
+
"pg": "^8.22.0",
|
|
82
|
+
"prisma": "^7.8.0",
|
|
83
|
+
"tsup": "^8.5.1",
|
|
84
|
+
"tsx": "^4.21.0",
|
|
85
|
+
"typescript": "^5.9.3"
|
|
86
|
+
},
|
|
87
|
+
"bin": {
|
|
88
|
+
"nano-app": "build/nano-app.js"
|
|
45
89
|
}
|
|
46
90
|
}
|
package/dist/app.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type NanoAppConfig } from "./index";
|
|
2
|
-
export declare class NanoApp {
|
|
3
|
-
private app;
|
|
4
|
-
private router;
|
|
5
|
-
private config;
|
|
6
|
-
constructor(config: NanoAppConfig);
|
|
7
|
-
getApp(): import("express-serve-static-core").Express;
|
|
8
|
-
start(listener?: () => void): Promise<void>;
|
|
9
|
-
}
|
package/dist/app.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
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.NanoApp = void 0;
|
|
7
|
-
const express_1 = __importDefault(require("express"));
|
|
8
|
-
const cors_1 = __importDefault(require("cors"));
|
|
9
|
-
const helmet_1 = __importDefault(require("helmet"));
|
|
10
|
-
const compression_1 = __importDefault(require("compression"));
|
|
11
|
-
const morgan_1 = __importDefault(require("morgan"));
|
|
12
|
-
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
13
|
-
const index_1 = require("./index");
|
|
14
|
-
const ApiKeys_1 = require("./auth/ApiKeys");
|
|
15
|
-
const ApiKeysEndpoint_1 = require("./auth/ApiKeysEndpoint");
|
|
16
|
-
class NanoApp {
|
|
17
|
-
app = (0, express_1.default)();
|
|
18
|
-
router;
|
|
19
|
-
config = {
|
|
20
|
-
endpoints: [],
|
|
21
|
-
enableEntities: true,
|
|
22
|
-
port: 3000,
|
|
23
|
-
rateLimit: {
|
|
24
|
-
windowMs: 15 * 60 * 1000,
|
|
25
|
-
max: 1000,
|
|
26
|
-
message: 'Too many requests, please try again later.',
|
|
27
|
-
},
|
|
28
|
-
morgan: {
|
|
29
|
-
format: 'combined',
|
|
30
|
-
},
|
|
31
|
-
expressConfig: {
|
|
32
|
-
limit: '10kb',
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
constructor(config) {
|
|
36
|
-
this.config = { ...this.config, ...config, apiKeys: config.apiKeys };
|
|
37
|
-
const apiKeysPath = this.config.apiKeys?.path ?? '/api-keys';
|
|
38
|
-
const apiKeyEndpoints = this.config.apiKeys?.enabled ? [new ApiKeysEndpoint_1.ApiKeysEndpoint(apiKeysPath)] : [];
|
|
39
|
-
const entityEndpoints = this.config.enableEntities ? [new index_1.Entities('/:endpoint{/:id}')] : [];
|
|
40
|
-
console.log('Endpoints registered:');
|
|
41
|
-
this.router = new index_1.Router([...apiKeyEndpoints, ...this.config.endpoints, ...entityEndpoints]);
|
|
42
|
-
if (this.config.setters) {
|
|
43
|
-
Object.entries(this.config.setters).forEach(([key, value]) => this.app.set(key, value));
|
|
44
|
-
}
|
|
45
|
-
Object.entries(this.config.morgan?.tokens ?? {}).forEach(([key, value]) => morgan_1.default.token(key, value));
|
|
46
|
-
this.app.use((0, morgan_1.default)(this.config.morgan?.format ?? 'combined'));
|
|
47
|
-
this.app.use((0, cors_1.default)());
|
|
48
|
-
this.app.use((0, helmet_1.default)());
|
|
49
|
-
this.app.use((0, compression_1.default)());
|
|
50
|
-
this.app.use((0, express_rate_limit_1.default)(this.config.rateLimit));
|
|
51
|
-
this.app.use(express_1.default.json(this.config.expressConfig));
|
|
52
|
-
this.app.use(express_1.default.urlencoded({ extended: false }));
|
|
53
|
-
}
|
|
54
|
-
getApp() { return this.app; }
|
|
55
|
-
async start(listener) {
|
|
56
|
-
if (this.config.apiKeys?.enabled) {
|
|
57
|
-
await ApiKeys_1.ApiKeys.init();
|
|
58
|
-
this.app.use(ApiKeys_1.ApiKeys.buildMiddleware(this.config.apiKeys.middleware));
|
|
59
|
-
}
|
|
60
|
-
(this.config.middlewares ?? []).forEach(middleware => this.app.use(middleware));
|
|
61
|
-
this.app.use('/', this.router.getRoutes());
|
|
62
|
-
this.app.use(this.config.errorMiddleware ?? ((err, _, res, __) => {
|
|
63
|
-
res.status(err.status || 500).json({
|
|
64
|
-
success: false,
|
|
65
|
-
message: err.message || 'Error interno del servidor',
|
|
66
|
-
});
|
|
67
|
-
}));
|
|
68
|
-
this.app.listen(Number(this.config.port), () => {
|
|
69
|
-
console.log(`Server running on port ${this.config.port}`);
|
|
70
|
-
listener?.();
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
exports.NanoApp = NanoApp;
|
package/dist/auth/ApiKeys.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from "express";
|
|
2
|
-
export declare class ApiKeys {
|
|
3
|
-
private static pool;
|
|
4
|
-
private static initialized;
|
|
5
|
-
static init(): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Construye el middleware de autenticación:
|
|
8
|
-
* - Sin customMiddleware → valida solo x-api-key.
|
|
9
|
-
* - Con customMiddleware → intenta primero el middleware custom.
|
|
10
|
-
* Si falla, intenta x-api-key. Si ambos fallan → 401.
|
|
11
|
-
*/
|
|
12
|
-
static buildMiddleware(customMiddleware?: RequestHandler): RequestHandler;
|
|
13
|
-
static create(label?: string): Promise<string>;
|
|
14
|
-
static validate(key: string): Promise<boolean>;
|
|
15
|
-
static list(): Promise<{
|
|
16
|
-
id: number;
|
|
17
|
-
label: string | null;
|
|
18
|
-
created_at: Date;
|
|
19
|
-
}[]>;
|
|
20
|
-
static revoke(id: number): Promise<void>;
|
|
21
|
-
private static validateApiKey;
|
|
22
|
-
private static runMiddleware;
|
|
23
|
-
private static printInitialKey;
|
|
24
|
-
private static assertInitialized;
|
|
25
|
-
}
|
package/dist/auth/ApiKeys.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ApiKeys = void 0;
|
|
37
|
-
const bcrypt = __importStar(require("bcrypt"));
|
|
38
|
-
const pg_1 = require("pg");
|
|
39
|
-
const crypto_1 = require("crypto");
|
|
40
|
-
const SCHEMA = "nano_auth";
|
|
41
|
-
const TABLE = `${SCHEMA}.api_keys`;
|
|
42
|
-
class ApiKeys {
|
|
43
|
-
static pool;
|
|
44
|
-
static initialized = false;
|
|
45
|
-
static async init() {
|
|
46
|
-
if (ApiKeys.initialized)
|
|
47
|
-
return;
|
|
48
|
-
const connectionString = process.env.DATABASE_URL;
|
|
49
|
-
if (!connectionString)
|
|
50
|
-
throw new Error("DATABASE_URL no está configurada");
|
|
51
|
-
ApiKeys.pool = new pg_1.Pool({ connectionString });
|
|
52
|
-
ApiKeys.initialized = true;
|
|
53
|
-
await ApiKeys.pool.query(`CREATE SCHEMA IF NOT EXISTS ${SCHEMA}`);
|
|
54
|
-
await ApiKeys.pool.query(`
|
|
55
|
-
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
56
|
-
id SERIAL PRIMARY KEY,
|
|
57
|
-
label TEXT,
|
|
58
|
-
key_hash TEXT NOT NULL,
|
|
59
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
60
|
-
)
|
|
61
|
-
`);
|
|
62
|
-
await ApiKeys.printInitialKey();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Construye el middleware de autenticación:
|
|
66
|
-
* - Sin customMiddleware → valida solo x-api-key.
|
|
67
|
-
* - Con customMiddleware → intenta primero el middleware custom.
|
|
68
|
-
* Si falla, intenta x-api-key. Si ambos fallan → 401.
|
|
69
|
-
*/
|
|
70
|
-
static buildMiddleware(customMiddleware) {
|
|
71
|
-
return async (req, res, next) => {
|
|
72
|
-
if (!customMiddleware) {
|
|
73
|
-
return ApiKeys.validateApiKey(req, res, next);
|
|
74
|
-
}
|
|
75
|
-
const customResult = await ApiKeys.runMiddleware(customMiddleware, req, res);
|
|
76
|
-
if (customResult.passed)
|
|
77
|
-
return next();
|
|
78
|
-
const key = req.headers['x-api-key'];
|
|
79
|
-
if (key && await ApiKeys.validate(key).catch(() => false)) {
|
|
80
|
-
req.apiKeyAuth = true;
|
|
81
|
-
return next();
|
|
82
|
-
}
|
|
83
|
-
return next(customResult.error ?? { status: 401, message: "No autorizado" });
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
static async create(label) {
|
|
87
|
-
ApiKeys.assertInitialized();
|
|
88
|
-
const key = `nk_${(0, crypto_1.randomBytes)(32).toString("hex")}`;
|
|
89
|
-
await ApiKeys.pool.query(`INSERT INTO ${TABLE} (label, key_hash) VALUES ($1, $2)`, [label ?? null, await bcrypt.hash(key, 10)]);
|
|
90
|
-
return key;
|
|
91
|
-
}
|
|
92
|
-
static async validate(key) {
|
|
93
|
-
ApiKeys.assertInitialized();
|
|
94
|
-
if (!key?.startsWith("nk_"))
|
|
95
|
-
return false;
|
|
96
|
-
const { rows } = await ApiKeys.pool.query(`SELECT key_hash FROM ${TABLE}`);
|
|
97
|
-
for (const row of rows) {
|
|
98
|
-
if (await bcrypt.compare(key, row.key_hash))
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
static async list() {
|
|
104
|
-
ApiKeys.assertInitialized();
|
|
105
|
-
const { rows } = await ApiKeys.pool.query(`SELECT id, label, created_at FROM ${TABLE} ORDER BY created_at DESC`);
|
|
106
|
-
return rows;
|
|
107
|
-
}
|
|
108
|
-
static async revoke(id) {
|
|
109
|
-
ApiKeys.assertInitialized();
|
|
110
|
-
await ApiKeys.pool.query(`DELETE FROM ${TABLE} WHERE id = $1`, [id]);
|
|
111
|
-
}
|
|
112
|
-
static async validateApiKey(req, _res, next) {
|
|
113
|
-
const key = req.headers['x-api-key'];
|
|
114
|
-
if (!key)
|
|
115
|
-
return next({ status: 401, message: "No autorizado" });
|
|
116
|
-
const valid = await ApiKeys.validate(key).catch(() => false);
|
|
117
|
-
if (valid) {
|
|
118
|
-
req.apiKeyAuth = true;
|
|
119
|
-
return next();
|
|
120
|
-
}
|
|
121
|
-
return next({ status: 401, message: "API key inválida" });
|
|
122
|
-
}
|
|
123
|
-
static async runMiddleware(middleware, req, res) {
|
|
124
|
-
return new Promise((resolve) => {
|
|
125
|
-
middleware(req, res, (err) => {
|
|
126
|
-
resolve(err ? { passed: false, error: err } : { passed: true });
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
static async printInitialKey() {
|
|
131
|
-
const { rows } = await ApiKeys.pool.query(`SELECT id FROM ${TABLE} LIMIT 1`);
|
|
132
|
-
if (rows.length > 0)
|
|
133
|
-
return;
|
|
134
|
-
const key = await ApiKeys.create('default');
|
|
135
|
-
console.log("\n══════════════════════════════════════════════════════");
|
|
136
|
-
console.log(" NANO-APP: API Key inicial generada ");
|
|
137
|
-
console.log(" Guarda esta key, no se volverá a mostrar. ");
|
|
138
|
-
console.log("══════════════════════════════════════════════════════");
|
|
139
|
-
console.log(`\n x-api-key: ${key}\n`);
|
|
140
|
-
}
|
|
141
|
-
static assertInitialized() {
|
|
142
|
-
if (!ApiKeys.initialized)
|
|
143
|
-
throw new Error("ApiKeys no inicializado.");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
exports.ApiKeys = ApiKeys;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Request, Response } from "express";
|
|
2
|
-
import { Endpoint } from "../http/Endpoint";
|
|
3
|
-
import type { ApiResponse } from "../types/api";
|
|
4
|
-
/**
|
|
5
|
-
* Endpoint predefinido para gestión de API keys.
|
|
6
|
-
* Registrado automáticamente por NanoApp en /api-keys.
|
|
7
|
-
*
|
|
8
|
-
* GET /api-keys — lista todas las keys (id, label, created_at)
|
|
9
|
-
* POST /api-keys — crea una nueva key { label?: string }
|
|
10
|
-
* DELETE /api-keys — revoca una key por { id: number }
|
|
11
|
-
*
|
|
12
|
-
* Para deshabilitar estos endpoints, usa apiKeys: { enabled: false } en NanoAppConfig.
|
|
13
|
-
* Para protegerlos con tu propia lógica, subclasifica y sobreescribe VALIDATE o BEFORE.
|
|
14
|
-
*/
|
|
15
|
-
export declare class ApiKeysEndpoint extends Endpoint {
|
|
16
|
-
protected GET(_req: Request, _res: Response): Promise<ApiResponse>;
|
|
17
|
-
protected POST(req: Request, _res: Response): Promise<ApiResponse>;
|
|
18
|
-
protected DELETE(req: Request, _res: Response): Promise<ApiResponse>;
|
|
19
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApiKeysEndpoint = void 0;
|
|
4
|
-
const Endpoint_1 = require("../http/Endpoint");
|
|
5
|
-
const ApiKeys_1 = require("./ApiKeys");
|
|
6
|
-
/**
|
|
7
|
-
* Endpoint predefinido para gestión de API keys.
|
|
8
|
-
* Registrado automáticamente por NanoApp en /api-keys.
|
|
9
|
-
*
|
|
10
|
-
* GET /api-keys — lista todas las keys (id, label, created_at)
|
|
11
|
-
* POST /api-keys — crea una nueva key { label?: string }
|
|
12
|
-
* DELETE /api-keys — revoca una key por { id: number }
|
|
13
|
-
*
|
|
14
|
-
* Para deshabilitar estos endpoints, usa apiKeys: { enabled: false } en NanoAppConfig.
|
|
15
|
-
* Para protegerlos con tu propia lógica, subclasifica y sobreescribe VALIDATE o BEFORE.
|
|
16
|
-
*/
|
|
17
|
-
class ApiKeysEndpoint extends Endpoint_1.Endpoint {
|
|
18
|
-
async GET(_req, _res) {
|
|
19
|
-
const keys = await ApiKeys_1.ApiKeys.list();
|
|
20
|
-
return { result: keys };
|
|
21
|
-
}
|
|
22
|
-
async POST(req, _res) {
|
|
23
|
-
const { label } = req.body ?? {};
|
|
24
|
-
const key = await ApiKeys_1.ApiKeys.create(label);
|
|
25
|
-
return { result: { api_key: key } };
|
|
26
|
-
}
|
|
27
|
-
async DELETE(req, _res) {
|
|
28
|
-
const { id } = req.body ?? {};
|
|
29
|
-
if (!id || typeof id !== "number") {
|
|
30
|
-
throw { status: 400, message: "El campo id es requerido y debe ser un número" };
|
|
31
|
-
}
|
|
32
|
-
await ApiKeys_1.ApiKeys.revoke(id);
|
|
33
|
-
return { result: { revoked: id } };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
exports.ApiKeysEndpoint = ApiKeysEndpoint;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from "express";
|
|
2
|
-
import type { NanoAppAuthConfig } from "../index";
|
|
3
|
-
export declare class ClientCredentials {
|
|
4
|
-
private static config;
|
|
5
|
-
private static oauthSecret;
|
|
6
|
-
private static pool;
|
|
7
|
-
static init(config: NanoAppAuthConfig): Promise<void>;
|
|
8
|
-
static middleware: RequestHandler;
|
|
9
|
-
static generateToken(clientId: string, clientSecret: string): Promise<{
|
|
10
|
-
access_token: string;
|
|
11
|
-
token_type: string;
|
|
12
|
-
expires_in: number;
|
|
13
|
-
}>;
|
|
14
|
-
private static parseExpiry;
|
|
15
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ClientCredentials = void 0;
|
|
37
|
-
const jwt = __importStar(require("jsonwebtoken"));
|
|
38
|
-
const bcrypt = __importStar(require("bcrypt"));
|
|
39
|
-
const pg_1 = require("pg");
|
|
40
|
-
const crypto_1 = require("crypto");
|
|
41
|
-
const SCHEMA = "nano_auth";
|
|
42
|
-
const TABLE = `${SCHEMA}.oauth_client`;
|
|
43
|
-
class ClientCredentials {
|
|
44
|
-
static config = {};
|
|
45
|
-
static oauthSecret;
|
|
46
|
-
static pool;
|
|
47
|
-
static async init(config) {
|
|
48
|
-
// Inicializar la configuración
|
|
49
|
-
const connectionString = process.env.DATABASE_URL ?? '';
|
|
50
|
-
ClientCredentials.config = config;
|
|
51
|
-
ClientCredentials.oauthSecret = process.env.OAUTH_SECRET ?? '';
|
|
52
|
-
// Crear el pool de conexiones
|
|
53
|
-
ClientCredentials.pool = new pg_1.Pool({ connectionString });
|
|
54
|
-
// Crear el schema si no existe
|
|
55
|
-
await ClientCredentials.pool.query(`CREATE SCHEMA IF NOT EXISTS ${SCHEMA}`);
|
|
56
|
-
await ClientCredentials.pool.query(`
|
|
57
|
-
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
58
|
-
id SERIAL PRIMARY KEY,
|
|
59
|
-
client_id TEXT NOT NULL UNIQUE,
|
|
60
|
-
client_secret TEXT NOT NULL,
|
|
61
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
62
|
-
)
|
|
63
|
-
`);
|
|
64
|
-
// Verificar si existen credenciales
|
|
65
|
-
const { rows } = await ClientCredentials.pool.query(`SELECT id FROM ${TABLE} LIMIT 1`);
|
|
66
|
-
if (rows.length === 0) {
|
|
67
|
-
// Generar credenciales
|
|
68
|
-
const clientId = (0, crypto_1.randomBytes)(16).toString("hex");
|
|
69
|
-
const clientSecret = (0, crypto_1.randomBytes)(32).toString("hex");
|
|
70
|
-
const hashed = await bcrypt.hash(clientSecret, 10);
|
|
71
|
-
// Insertar credenciales
|
|
72
|
-
await ClientCredentials.pool.query(`INSERT INTO ${TABLE} (client_id, client_secret) VALUES ($1, $2)`, [clientId, hashed]);
|
|
73
|
-
// Mostrar credenciales
|
|
74
|
-
console.log("\n══════════════════════════════════════════════════════");
|
|
75
|
-
console.log(" NANO-APP: OAuth Client Credentials ");
|
|
76
|
-
console.log(" Guarda estas credenciales, no se mostrarán más. ");
|
|
77
|
-
console.log("══════════════════════════════════════════════════════\n");
|
|
78
|
-
console.log(`client_id: ${clientId}`);
|
|
79
|
-
console.log(`client_secret: ${clientSecret}\n\n`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
static middleware = async (req, res, next) => {
|
|
83
|
-
const publicRoutes = ["/oauth/token", ...(ClientCredentials.config.publicRoutes ?? [])];
|
|
84
|
-
// Si la ruta es pública, continuar
|
|
85
|
-
if (publicRoutes.includes(req.path)) {
|
|
86
|
-
return next();
|
|
87
|
-
}
|
|
88
|
-
// Validar que el header de autenticación sea válido
|
|
89
|
-
const authHeader = req.headers.authorization;
|
|
90
|
-
if (!authHeader?.startsWith("Bearer ")) {
|
|
91
|
-
return next({ status: 401, message: "Header de autenticación inválido" });
|
|
92
|
-
}
|
|
93
|
-
// Obtener el token y secret
|
|
94
|
-
const token = authHeader.slice(7);
|
|
95
|
-
// Verificar que el token sea válido
|
|
96
|
-
try {
|
|
97
|
-
const payload = jwt.verify(token, ClientCredentials.oauthSecret);
|
|
98
|
-
req.oauthClient = { clientId: payload.sub };
|
|
99
|
-
return next();
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return next({ status: 401, message: "Sesión expirada" });
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
static async generateToken(clientId, clientSecret) {
|
|
106
|
-
if (!ClientCredentials.pool) {
|
|
107
|
-
throw { status: 500, message: "ClientCredentials no inicializado" };
|
|
108
|
-
}
|
|
109
|
-
const { rows } = await ClientCredentials.pool.query(`SELECT client_id, client_secret FROM ${TABLE} WHERE client_id = $1 LIMIT 1`, [clientId]);
|
|
110
|
-
if (rows.length === 0) {
|
|
111
|
-
throw { status: 401, message: "Credenciales de cliente inválidas" };
|
|
112
|
-
}
|
|
113
|
-
const valid = await bcrypt.compare(clientSecret, rows[0].client_secret);
|
|
114
|
-
if (!valid) {
|
|
115
|
-
throw { status: 401, message: "Credenciales de cliente inválidas" };
|
|
116
|
-
}
|
|
117
|
-
const expiry = ClientCredentials.config.tokenExpiry ?? '1h';
|
|
118
|
-
const expiresInSeconds = ClientCredentials.parseExpiry(expiry);
|
|
119
|
-
const access_token = jwt.sign({ sub: rows[0].client_id }, ClientCredentials.oauthSecret, { expiresIn: expiry });
|
|
120
|
-
return { access_token, token_type: "Bearer", expires_in: expiresInSeconds };
|
|
121
|
-
}
|
|
122
|
-
static parseExpiry(expiry) {
|
|
123
|
-
const match = expiry.match(/^(\d+)(s|m|h|d)$/);
|
|
124
|
-
if (!match)
|
|
125
|
-
return 3600;
|
|
126
|
-
const value = parseInt(match[1], 10);
|
|
127
|
-
const unit = match[2];
|
|
128
|
-
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
129
|
-
return value * (multipliers[unit] ?? 3600);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
exports.ClientCredentials = ClientCredentials;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TokenEndpoint = void 0;
|
|
4
|
-
const index_1 = require("../index");
|
|
5
|
-
class TokenEndpoint extends index_1.Endpoint {
|
|
6
|
-
async POST(req, res) {
|
|
7
|
-
let clientId;
|
|
8
|
-
let clientSecret;
|
|
9
|
-
// Authorization: Basic <base64(clientId:clientSecret)>
|
|
10
|
-
const authHeader = req.headers.authorization;
|
|
11
|
-
if (authHeader?.startsWith("Basic ")) {
|
|
12
|
-
const decoded = Buffer.from(authHeader.slice(6), "base64").toString("utf-8");
|
|
13
|
-
const colon = decoded.indexOf(":");
|
|
14
|
-
if (colon !== -1) {
|
|
15
|
-
clientId = decoded.slice(0, colon);
|
|
16
|
-
clientSecret = decoded.slice(colon + 1);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
// Body: client_id y client_secret (JSON o form-urlencoded)
|
|
20
|
-
if (!clientId || !clientSecret) {
|
|
21
|
-
clientId = req.body?.client_id;
|
|
22
|
-
clientSecret = req.body?.client_secret;
|
|
23
|
-
}
|
|
24
|
-
if (!clientId || !clientSecret) {
|
|
25
|
-
throw { status: 400, message: "client_id y client_secret son campos requeridos" };
|
|
26
|
-
}
|
|
27
|
-
const tokenData = await index_1.ClientCredentials.generateToken(clientId, clientSecret);
|
|
28
|
-
// RFC 6749 §5.1: la respuesta del token endpoint debe ser plana, sin wrappers
|
|
29
|
-
res.json(tokenData);
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
exports.TokenEndpoint = TokenEndpoint;
|
package/dist/http/Endpoint.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from "express";
|
|
2
|
-
import { PrismaClient } from "../prisma/Client";
|
|
3
|
-
import type { ApiResponse } from "../types/api";
|
|
4
|
-
export declare abstract class Endpoint<BeforeResult = any> {
|
|
5
|
-
protected prismaClient: PrismaClient;
|
|
6
|
-
path: string;
|
|
7
|
-
private methodMap;
|
|
8
|
-
constructor(path?: string);
|
|
9
|
-
protected VALIDATE(_req: Request): Promise<void>;
|
|
10
|
-
protected BEFORE(_req: Request): Promise<BeforeResult>;
|
|
11
|
-
protected AFTER(_req: Request, result: ApiResponse): Promise<ApiResponse>;
|
|
12
|
-
protected GET(_req: Request, _res: Response, _before: BeforeResult): Promise<ApiResponse>;
|
|
13
|
-
protected POST(_req: Request, _res: Response, _before: BeforeResult): Promise<ApiResponse>;
|
|
14
|
-
protected PUT(_req: Request, _res: Response, _before: BeforeResult): Promise<ApiResponse>;
|
|
15
|
-
protected DELETE(_req: Request, _res: Response, _before: BeforeResult): Promise<ApiResponse>;
|
|
16
|
-
execute(req: Request, res: Response): void;
|
|
17
|
-
}
|