@ecdt/server-common 1.0.5 → 1.1.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 +27 -3
- package/package.json +1 -1
- package/src/index.d.ts +18 -5
- package/src/index.js +2 -2
- package/src/services/corsService.js +16 -6
- package/src/services/tokenService.js +59 -23
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Conjunto de ferramentas e configurações comuns para servidores Express da Econ
|
|
|
8
8
|
|
|
9
9
|
`@ecdt/server-common` fornece um conjunto de middlewares pré-configurados para aplicações Express, eliminando a necessidade de configurar manualmente o parsing de requisições e a sanitização de tokens em cada serviço.
|
|
10
10
|
|
|
11
|
-
### `expressCommonMiddlewares()`
|
|
11
|
+
### `expressCommonMiddlewares(options?)`
|
|
12
12
|
|
|
13
13
|
Retorna um array com os seguintes middlewares já configurados, prontos para serem aplicados no Express via `app.use()`:
|
|
14
14
|
|
|
@@ -26,7 +26,24 @@ Este middleware normaliza tokens de autenticação que chegam codificados na URL
|
|
|
26
26
|
1. O header `Authorization` — se presente, sanitiza o valor diretamente.
|
|
27
27
|
2. O cookie da requisição — se não houver `Authorization`, extrai o token do cookie de sessão e o define em `req.headers.authorization`.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
#### Resolução do nome do cookie de sessão
|
|
30
|
+
|
|
31
|
+
Por padrão, o nome do cookie é resolvido **por requisição**, a partir da origem (`Origin` > `Referer` > `Host`):
|
|
32
|
+
|
|
33
|
+
- Origem com subdomínio `hml` (ex: `hml.econodata.com.br`, `api.hml.econodata.com.br`, `hml-site.econodata.com.br`) → cookie `hml-ecdt_token_site`
|
|
34
|
+
- Demais origens → cookie `ecdt_token_site`
|
|
35
|
+
|
|
36
|
+
Em ambos os casos, se o cookie preferido não existir na requisição, o outro nome é usado como fallback. A busca é por **match exato** do nome do cookie.
|
|
37
|
+
|
|
38
|
+
Também é possível fixar o nome explicitamente via `options.cookieName` (string ou array em ordem de preferência), ignorando a detecção por origem:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// Nome fixo
|
|
42
|
+
app.use(...expressCommonMiddlewares({ cookieName: "hml-ecdt_token_site" }));
|
|
43
|
+
|
|
44
|
+
// Múltiplos nomes, em ordem de preferência
|
|
45
|
+
app.use(...expressCommonMiddlewares({ cookieName: ["hml-ecdt_token_site", "ecdt_token_site"] }));
|
|
46
|
+
```
|
|
30
47
|
|
|
31
48
|
### `expressCors(options)`
|
|
32
49
|
|
|
@@ -34,7 +51,8 @@ Retorna o middleware de CORS padrão dos servidores Econodata (baseado no pacote
|
|
|
34
51
|
|
|
35
52
|
| Opção | Tipo | Descrição |
|
|
36
53
|
|---|---|---|
|
|
37
|
-
| `origins` | `string[]`
|
|
54
|
+
| `origins` | `string[]` | Origins permitidas. Requisições de outras origins não recebem os headers CORS (o navegador bloqueia a resposta). Obrigatório se `econodataOrigins` não for `true` |
|
|
55
|
+
| `econodataOrigins` | `boolean` | Permite todas as origins `https` do domínio `econodata.com.br` (qualquer subdomínio, ex: `https://app.econodata.com.br`, `https://api.hml.econodata.com.br`) |
|
|
38
56
|
| `extraHeaders` | `string[]` | Headers adicionais além dos padrões |
|
|
39
57
|
| `headers` | `string[]` | Lista **exata** de headers permitidos — substitui os padrões. Não combinar com `extraHeaders` |
|
|
40
58
|
| `methods` | `string[]` | Lista **exata** de métodos permitidos. Default: todos (`GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS`) |
|
|
@@ -48,6 +66,12 @@ const { expressCors } = require("@ecdt/server-common");
|
|
|
48
66
|
// Configuração mínima
|
|
49
67
|
app.use(expressCors({ origins: ["https://app.econodata.com.br"] }));
|
|
50
68
|
|
|
69
|
+
// Todas as origins do domínio econodata.com.br (qualquer subdomínio)
|
|
70
|
+
app.use(expressCors({ econodataOrigins: true }));
|
|
71
|
+
|
|
72
|
+
// Combinando econodataOrigins com origins extras
|
|
73
|
+
app.use(expressCors({ econodataOrigins: true, origins: ["http://localhost:3000"] }));
|
|
74
|
+
|
|
51
75
|
// Headers extras além dos padrões
|
|
52
76
|
app.use(expressCors({ origins: [...], extraHeaders: ["X-Custom-Header"] }));
|
|
53
77
|
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
declare module '@ecdt/server-common' {
|
|
2
|
+
export interface ExpressCommonMiddlewaresOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Nome(s) do cookie de sessão, em ordem de preferência.
|
|
5
|
+
* Quando omitido, o nome é resolvido por requisição a partir da origem (origin > referer > host):
|
|
6
|
+
* origens com subdomínio "hml" usam `hml-ecdt_token_site`, as demais usam `ecdt_token_site`.
|
|
7
|
+
*/
|
|
8
|
+
cookieName?: string | string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
2
11
|
/**
|
|
3
|
-
* Aplica bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}) e devMktTokenSanitaze()
|
|
12
|
+
* Aplica bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}) e devMktTokenSanitaze()
|
|
4
13
|
* no middleware do express.
|
|
5
|
-
*
|
|
14
|
+
*
|
|
6
15
|
* Uso: app.use(...expressCommonMiddlewares())
|
|
7
16
|
*/
|
|
8
|
-
export function expressCommonMiddlewares(
|
|
17
|
+
export function expressCommonMiddlewares(
|
|
18
|
+
options?: ExpressCommonMiddlewaresOptions
|
|
19
|
+
): Array<(req: unknown, res: unknown, next: (err?: unknown) => void) => void>;
|
|
9
20
|
|
|
10
21
|
export interface ExpressCorsOptions {
|
|
11
|
-
/** Origins permitidas (ex: ["https://app.econodata.com.br"]). Obrigatório. */
|
|
12
|
-
origins
|
|
22
|
+
/** Origins permitidas (ex: ["https://app.econodata.com.br"]). Obrigatório se econodataOrigins não for true. */
|
|
23
|
+
origins?: string[];
|
|
24
|
+
/** Permite todas as origins https do domínio econodata.com.br (qualquer subdomínio). */
|
|
25
|
+
econodataOrigins?: boolean;
|
|
13
26
|
/** Headers adicionais além dos padrões (Authorization, Content-Type, X-Requested-With). */
|
|
14
27
|
extraHeaders?: string[];
|
|
15
28
|
/** Lista exata de headers permitidos — substitui os padrões. Não combinar com extraHeaders. */
|
package/src/index.js
CHANGED
|
@@ -2,8 +2,8 @@ const bodyParser = require("body-parser");
|
|
|
2
2
|
const { devMktTokenSanitaze } = require("./services/tokenService.js");
|
|
3
3
|
const { expressCors } = require("./services/corsService.js");
|
|
4
4
|
|
|
5
|
-
function expressCommonMiddlewares(){
|
|
6
|
-
return [bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}), devMktTokenSanitaze];
|
|
5
|
+
function expressCommonMiddlewares({ cookieName } = {}){
|
|
6
|
+
return [bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}), devMktTokenSanitaze({ cookieName })];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
|
|
@@ -2,17 +2,22 @@ const cors = require("cors");
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_HEADERS = ["Authorization", "Content-Type", "X-Requested-With"];
|
|
4
4
|
|
|
5
|
+
// Qualquer subdomínio (em qualquer nível) de econodata.com.br via https, incluindo o domínio raiz
|
|
6
|
+
const ECONODATA_ORIGINS_REGEX = /^https:\/\/([a-z0-9-]+\.)*econodata\.com\.br$/;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* @param {object} options
|
|
7
|
-
* @param {string[]} options.origins - Origins permitidas (ex: ["https://app.econodata.com.br"])
|
|
10
|
+
* @param {string[]} [options.origins] - Origins permitidas (ex: ["https://app.econodata.com.br"])
|
|
11
|
+
* @param {boolean} [options.econodataOrigins] - Permite todas as origins https do domínio econodata.com.br (qualquer subdomínio)
|
|
8
12
|
* @param {string[]} [options.extraHeaders] - Headers adicionais além dos padrões
|
|
9
13
|
* @param {string[]} [options.headers] - Lista exata de headers (ignora os padrões e extraHeaders)
|
|
10
14
|
* @param {string[]} [options.methods] - Lista exata de métodos permitidos (default: todos)
|
|
11
15
|
* @returns middleware do express
|
|
12
16
|
*/
|
|
13
|
-
function expressCors({ origins, extraHeaders, headers, methods } = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
function expressCors({ origins, econodataOrigins, extraHeaders, headers, methods } = {}) {
|
|
18
|
+
const hasOrigins = Array.isArray(origins) && origins.length > 0;
|
|
19
|
+
if (!hasOrigins && !econodataOrigins) {
|
|
20
|
+
throw new Error("expressCors: informe 'origins' (array com pelo menos uma origin) e/ou 'econodataOrigins: true'");
|
|
16
21
|
}
|
|
17
22
|
if (headers && extraHeaders) {
|
|
18
23
|
throw new Error("expressCors: use 'headers' OU 'extraHeaders', não os dois ao mesmo tempo");
|
|
@@ -20,12 +25,17 @@ function expressCors({ origins, extraHeaders, headers, methods } = {}) {
|
|
|
20
25
|
|
|
21
26
|
const allowedHeaders = headers ?? [...DEFAULT_HEADERS, ...(extraHeaders ?? [])];
|
|
22
27
|
|
|
28
|
+
const allowedOrigins = [
|
|
29
|
+
...(hasOrigins ? origins : []),
|
|
30
|
+
...(econodataOrigins ? [ECONODATA_ORIGINS_REGEX] : []),
|
|
31
|
+
];
|
|
32
|
+
|
|
23
33
|
return cors({
|
|
24
|
-
origin:
|
|
34
|
+
origin: allowedOrigins,
|
|
25
35
|
credentials: true,
|
|
26
36
|
allowedHeaders,
|
|
27
37
|
methods: methods ?? ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
|
|
28
38
|
});
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
module.exports = { expressCors, DEFAULT_HEADERS };
|
|
41
|
+
module.exports = { expressCors, DEFAULT_HEADERS, ECONODATA_ORIGINS_REGEX };
|
|
@@ -1,29 +1,65 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
function devMktTokenSanitaze(req, res, next) {
|
|
4
|
-
if(req?.headers == null) return next();
|
|
5
|
-
|
|
6
|
-
if (req.headers.authorization) {
|
|
7
|
-
req.headers.authorization = req.headers.authorization
|
|
8
|
-
.replace("+", " ")
|
|
9
|
-
.replace("%2B", " ");
|
|
10
|
-
} else if (req.headers.cookie) {
|
|
11
|
-
req.headers.authorization = `${getCookieSite(
|
|
12
|
-
req.headers.cookie
|
|
13
|
-
)}`;
|
|
14
|
-
}
|
|
1
|
+
const SITE_COOKIE_NAME = "ecdt_token_site";
|
|
2
|
+
const HML_SITE_COOKIE_NAME = `hml-${SITE_COOKIE_NAME}`;
|
|
15
3
|
|
|
16
|
-
|
|
17
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* @param {object} [options]
|
|
6
|
+
* @param {string|string[]} [options.cookieName] - Nome(s) do cookie de sessão, em ordem de preferência.
|
|
7
|
+
* Quando omitido, o nome é resolvido por requisição a partir da origem (origin > referer > host):
|
|
8
|
+
* origens com subdomínio "hml" usam o cookie de hml, as demais usam o de produção.
|
|
9
|
+
* @returns middleware do express
|
|
10
|
+
*/
|
|
11
|
+
function devMktTokenSanitaze({ cookieName } = {}) {
|
|
12
|
+
const fixedCookieNames = cookieName
|
|
13
|
+
? (Array.isArray(cookieName) ? cookieName : [cookieName])
|
|
14
|
+
: null;
|
|
15
|
+
|
|
16
|
+
return function devMktTokenSanitazeMiddleware(req, _res, next) {
|
|
17
|
+
if (req?.headers == null) return next();
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (req.headers.authorization) {
|
|
20
|
+
req.headers.authorization = req.headers.authorization
|
|
21
|
+
.replace("+", " ")
|
|
22
|
+
.replace("%2B", " ");
|
|
23
|
+
} else if (req.headers.cookie) {
|
|
24
|
+
const cookieNames = fixedCookieNames ?? resolveCookieNamesByRequest(req);
|
|
25
|
+
req.headers.authorization = getCookieValue(req.headers.cookie, cookieNames);
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
return next();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveCookieNamesByRequest(req) {
|
|
33
|
+
// O nome alternativo fica como fallback para o caso do cookie preferido não existir
|
|
34
|
+
return isHmlRequest(req)
|
|
35
|
+
? [HML_SITE_COOKIE_NAME, SITE_COOKIE_NAME]
|
|
36
|
+
: [SITE_COOKIE_NAME, HML_SITE_COOKIE_NAME];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isHmlRequest(req) {
|
|
40
|
+
const source = req.headers.origin ?? req.headers.referer ?? req.headers.host ?? "";
|
|
41
|
+
const hostname = String(source).replace(/^[a-z]+:\/\//i, "").split(/[/:?#]/)[0];
|
|
42
|
+
// "hml" como parte do hostname delimitado por ponto ou hífen
|
|
43
|
+
// (ex: hml.econodata.com.br, api.hml.econodata.com.br, hml-site.econodata.com.br)
|
|
44
|
+
return /(^|[.-])hml([.-]|$)/i.test(hostname);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getCookieValue(cookieHeader, cookieNames) {
|
|
48
|
+
const cookies = cookieHeader.split(";").map((entry) => {
|
|
49
|
+
const separatorIndex = entry.indexOf("=");
|
|
50
|
+
if (separatorIndex === -1) return { name: entry.trim(), value: "" };
|
|
51
|
+
return {
|
|
52
|
+
name: entry.slice(0, separatorIndex).trim(),
|
|
53
|
+
value: entry.slice(separatorIndex + 1).trim(),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
for (const name of cookieNames) {
|
|
58
|
+
const found = cookies.find((c) => c.name === name);
|
|
59
|
+
if (found) return found.value.replace("+", " ").replace("%2B", " ");
|
|
25
60
|
}
|
|
26
|
-
|
|
61
|
+
|
|
62
|
+
return "";
|
|
27
63
|
}
|
|
28
64
|
|
|
29
|
-
module.exports = {devMktTokenSanitaze}
|
|
65
|
+
module.exports = { devMktTokenSanitaze, SITE_COOKIE_NAME, HML_SITE_COOKIE_NAME };
|