@nano-app/backend 0.6.4 → 0.6.5
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nano-app/backend",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Backend for nano-app architecture",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
],
|
|
20
20
|
"author": "Cristian Serrano",
|
|
21
21
|
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/Nano-Nathan/nano-app.git"
|
|
25
|
+
},
|
|
22
26
|
"dependencies": {
|
|
23
27
|
"@prisma/adapter-pg": "^7.4.0",
|
|
24
28
|
"@prisma/client": "^7.4.0",
|
|
@@ -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;
|