@things-factory/shell 10.1.227 → 10.1.234
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/dist-server/middlewares/domain-middleware.js +4 -11
- package/dist-server/middlewares/domain-middleware.js.map +1 -1
- package/dist-server/middlewares/ip-list-rule.d.ts +10 -0
- package/dist-server/middlewares/ip-list-rule.js +55 -0
- package/dist-server/middlewares/ip-list-rule.js.map +1 -0
- package/dist-server/service/domain/domain-mutation.js +4 -1
- package/dist-server/service/domain/domain-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/tests/ip-list-rule.test.ts +87 -0
- package/translations/en.json +15 -1
- package/translations/es.json +9 -1
- package/translations/ja.json +9 -1
- package/translations/ko.json +15 -1
- package/translations/ms.json +9 -1
- package/translations/zh.json +9 -1
|
@@ -5,6 +5,7 @@ exports.domainMiddleware = domainMiddleware;
|
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
const request_ip_1 = tslib_1.__importDefault(require("request-ip"));
|
|
7
7
|
const get_domain_js_1 = require("../utils/get-domain.js");
|
|
8
|
+
const ip_list_rule_js_1 = require("./ip-list-rule.js");
|
|
8
9
|
/**
|
|
9
10
|
* The reason an address was refused, as a header the client can read.
|
|
10
11
|
*
|
|
@@ -36,27 +37,19 @@ async function domainMiddleware(context, next) {
|
|
|
36
37
|
const { whitelist = [], blacklist = [], protectedlist = [], privileges = [] } = domain.iplist || {};
|
|
37
38
|
if (Array.isArray(whitelist) && whitelist.length > 0) {
|
|
38
39
|
/* whitelist 우선 */
|
|
39
|
-
const whitelisted =
|
|
40
|
-
whitelist.some(item => {
|
|
41
|
-
return new RegExp(item).test(ip);
|
|
42
|
-
});
|
|
40
|
+
const whitelisted = (0, ip_list_rule_js_1.ipListMatches)(whitelist, ip);
|
|
43
41
|
if (!whitelisted) {
|
|
44
42
|
return refuseByAddress(context);
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
else {
|
|
48
|
-
const blacklisted = Array.isArray(blacklist) &&
|
|
49
|
-
blacklist.some(item => {
|
|
50
|
-
return new RegExp(item).test(ip);
|
|
51
|
-
});
|
|
46
|
+
const blacklisted = Array.isArray(blacklist) && (0, ip_list_rule_js_1.ipListMatches)(blacklist, ip);
|
|
52
47
|
if (blacklisted) {
|
|
53
48
|
return refuseByAddress(context);
|
|
54
49
|
}
|
|
55
50
|
}
|
|
56
51
|
if (Array.isArray(protectedlist) && protectedlist.length > 0) {
|
|
57
|
-
const safe =
|
|
58
|
-
return new RegExp(item).test(ip);
|
|
59
|
-
});
|
|
52
|
+
const safe = (0, ip_list_rule_js_1.ipListMatches)(protectedlist, ip);
|
|
60
53
|
context.state.unsafeIP = !safe;
|
|
61
54
|
if (!safe) {
|
|
62
55
|
/* `by` tells the refusal it was the address - a role would not open it (ADR-0103 decision 1). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain-middleware.js","sourceRoot":"","sources":["../../server/middlewares/domain-middleware.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"domain-middleware.js","sourceRoot":"","sources":["../../server/middlewares/domain-middleware.ts"],"names":[],"mappings":";;;AAsBA,4CA6CC;;AAnED,oEAAkC;AAClC,0DAAyD;AACzD,uDAAiD;AAEjD;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,WAAW,CAAA;AAC5B,QAAA,eAAe,GAAG,qBAAqB,CAAA;AAEpD,SAAS,eAAe,CAAC,OAAY;IACnC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;IACpB,OAAO,CAAC,GAAG,CAAC,sBAAc,EAAE,uBAAe,CAAC,CAAA;AAC9C,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,OAAY,EAAE,IAAS;IAC5D,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ;;;;WAIG;QACH,MAAM,GAAG,MAAM,IAAA,gCAAgB,EAAC,OAAO,CAAC,CAAA;IAC1C,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,EAAE,GAAG,oBAAS,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC7C,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,SAAS,GAAG,EAAE,EAAE,aAAa,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAA;QAEnG,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,kBAAkB;YAClB,MAAM,WAAW,GAAG,IAAA,+BAAa,EAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAEhD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,eAAe,CAAC,OAAO,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,IAAA,+BAAa,EAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAE5E,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,eAAe,CAAC,OAAO,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,GAAG,IAAA,+BAAa,EAAC,aAAa,EAAE,EAAE,CAAC,CAAA;YAE7C,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAA;YAE9B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,iGAAiG;gBACjG,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAC9H,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAA;IAE7B,OAAO,IAAI,EAAE,CAAA;AACf,CAAC","sourcesContent":["import requestIp from 'request-ip'\nimport { getDomainFromURL } from '../utils/get-domain.js'\nimport { ipListMatches } from './ip-list-rule.js'\n\n/**\n * The reason an address was refused, as a header the client can read.\n *\n * The domain's IP lists answer 403, and so does \"no domain chosen\" (domain-authenticate-middleware).\n * The client sent both to check-in; for a refused address check-in sends the person straight back,\n * so the screen stopped with nothing said (ADR README, plan 3). With this header the client can tell\n * the two apart and say what happened - the address is not let in, and check-in cannot change that.\n *\n * A header, not a body: it reaches the client whatever the request was (GraphQL, HTML, a file).\n */\nexport const REFUSAL_HEADER = 'x-refusal'\nexport const REFUSED_ADDRESS = 'address-not-allowed'\n\nfunction refuseByAddress(context: any) {\n context.status = 403\n context.set(REFUSAL_HEADER, REFUSED_ADDRESS)\n}\n\nexport async function domainMiddleware(context: any, next: any) {\n var { domain } = context.state\n if (!domain) {\n /*\n * The domainTypes should be checked only when signin and checkin.\n * For purposes such as API calls, the target domainTypes may be different from the system domainTypes.\n * So, we don't check domainTypes here.\n */\n domain = await getDomainFromURL(context)\n }\n\n if (domain) {\n const ip = requestIp.getClientIp(context.req)\n const { whitelist = [], blacklist = [], protectedlist = [], privileges = [] } = domain.iplist || {}\n\n if (Array.isArray(whitelist) && whitelist.length > 0) {\n /* whitelist 우선 */\n const whitelisted = ipListMatches(whitelist, ip)\n\n if (!whitelisted) {\n return refuseByAddress(context)\n }\n } else {\n const blacklisted = Array.isArray(blacklist) && ipListMatches(blacklist, ip)\n\n if (blacklisted) {\n return refuseByAddress(context)\n }\n }\n\n if (Array.isArray(protectedlist) && protectedlist.length > 0) {\n const safe = ipListMatches(protectedlist, ip)\n\n context.state.unsafeIP = !safe\n\n if (!safe) {\n /* `by` tells the refusal it was the address - a role would not open it (ADR-0103 decision 1). */\n context.state.prohibitedPrivileges = (Array.isArray(privileges) ? privileges : []).map(item => ({ ...item, by: 'address' }))\n }\n }\n }\n\n context.state.domain = domain\n\n return next()\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A domain's IP lists hold regular expressions, matched against the client address as they are written
|
|
3
|
+
* (`^10\.` for a prefix). One entry that is not a regular expression used to throw on every request of the
|
|
4
|
+
* domain, so the lists are checked when they are saved, and a bad entry already stored is passed over.
|
|
5
|
+
*/
|
|
6
|
+
export declare const IP_LISTS: readonly ["whitelist", "blacklist", "protectedlist"];
|
|
7
|
+
/** True when an entry of the list matches the address. A stored entry that does not compile matches nothing. */
|
|
8
|
+
export declare function ipListMatches(entries: unknown[], ip: string): boolean;
|
|
9
|
+
/** Refuses the first entry, of whichever list, that is blank or not a regular expression - naming the list and the entry. */
|
|
10
|
+
export declare function refuseBadIPList(iplist: Record<string, unknown> | null | undefined): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IP_LISTS = void 0;
|
|
4
|
+
exports.ipListMatches = ipListMatches;
|
|
5
|
+
exports.refuseBadIPList = refuseBadIPList;
|
|
6
|
+
const env_1 = require("@things-factory/env");
|
|
7
|
+
const refusal_js_1 = require("../refusal/refusal.js");
|
|
8
|
+
/**
|
|
9
|
+
* A domain's IP lists hold regular expressions, matched against the client address as they are written
|
|
10
|
+
* (`^10\.` for a prefix). One entry that is not a regular expression used to throw on every request of the
|
|
11
|
+
* domain, so the lists are checked when they are saved, and a bad entry already stored is passed over.
|
|
12
|
+
*/
|
|
13
|
+
exports.IP_LISTS = ['whitelist', 'blacklist', 'protectedlist'];
|
|
14
|
+
const compiled = new Map();
|
|
15
|
+
function patternOf(entry) {
|
|
16
|
+
if (!compiled.has(entry)) {
|
|
17
|
+
try {
|
|
18
|
+
compiled.set(entry, new RegExp(entry));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
env_1.logger.warn(`ip list entry "${entry}" is not a regular expression and matches nothing`);
|
|
22
|
+
compiled.set(entry, null);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return compiled.get(entry);
|
|
26
|
+
}
|
|
27
|
+
/** True when an entry of the list matches the address. A stored entry that does not compile matches nothing. */
|
|
28
|
+
function ipListMatches(entries, ip) {
|
|
29
|
+
return entries.some(entry => typeof entry === 'string' && !!patternOf(entry)?.test(ip));
|
|
30
|
+
}
|
|
31
|
+
/** Refuses the first entry, of whichever list, that is blank or not a regular expression - naming the list and the entry. */
|
|
32
|
+
function refuseBadIPList(iplist) {
|
|
33
|
+
for (const list of exports.IP_LISTS) {
|
|
34
|
+
const entries = iplist?.[list];
|
|
35
|
+
if (entries == null)
|
|
36
|
+
continue;
|
|
37
|
+
if (!Array.isArray(entries)) {
|
|
38
|
+
throw new refusal_js_1.Refusal(`ip-${list}-entry-invalid`, { entry: String(entries) }, `"${entries}" in the ${list} is not a regular expression`);
|
|
39
|
+
}
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (typeof entry === 'string' && !entry.trim()) {
|
|
42
|
+
throw new refusal_js_1.Refusal(`ip-${list}-entry-empty`, {}, `the ${list} has an empty entry`);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
if (typeof entry !== 'string')
|
|
46
|
+
throw new TypeError();
|
|
47
|
+
new RegExp(entry);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
throw new refusal_js_1.Refusal(`ip-${list}-entry-invalid`, { entry: String(entry) }, `"${entry}" in the ${list} is not a regular expression`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=ip-list-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-list-rule.js","sourceRoot":"","sources":["../../server/middlewares/ip-list-rule.ts"],"names":[],"mappings":";;;AA2BA,sCAEC;AAGD,0CAoBC;AApDD,6CAA4C;AAE5C,sDAA+C;AAE/C;;;;GAIG;AAEU,QAAA,QAAQ,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,eAAe,CAAU,CAAA;AAE5E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAA;AAEjD,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACxC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,YAAM,CAAC,IAAI,CAAC,kBAAkB,KAAK,mDAAmD,CAAC,CAAA;YACvF,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,gHAAgH;AAChH,SAAgB,aAAa,CAAC,OAAkB,EAAE,EAAU;IAC1D,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AACzF,CAAC;AAED,6HAA6H;AAC7H,SAAgB,eAAe,CAAC,MAAkD;IAChF,KAAK,MAAM,IAAI,IAAI,gBAAQ,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,OAAO,IAAI,IAAI;YAAE,SAAQ;QAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,oBAAO,CAAC,MAAM,IAAI,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,OAAO,YAAY,IAAI,8BAA8B,CAAC,CAAA;QACtI,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC/C,MAAM,IAAI,oBAAO,CAAC,MAAM,IAAI,cAAc,EAAE,EAAE,EAAE,OAAO,IAAI,qBAAqB,CAAC,CAAA;YACnF,CAAC;YACD,IAAI,CAAC;gBACH,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAAE,MAAM,IAAI,SAAS,EAAE,CAAA;gBACpD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAA;YACnB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,oBAAO,CAAC,MAAM,IAAI,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,KAAK,YAAY,IAAI,8BAA8B,CAAC,CAAA;YAClI,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { logger } from '@things-factory/env'\n\nimport { Refusal } from '../refusal/refusal.js'\n\n/**\n * A domain's IP lists hold regular expressions, matched against the client address as they are written\n * (`^10\\.` for a prefix). One entry that is not a regular expression used to throw on every request of the\n * domain, so the lists are checked when they are saved, and a bad entry already stored is passed over.\n */\n\nexport const IP_LISTS = ['whitelist', 'blacklist', 'protectedlist'] as const\n\nconst compiled = new Map<string, RegExp | null>()\n\nfunction patternOf(entry: string): RegExp | null {\n if (!compiled.has(entry)) {\n try {\n compiled.set(entry, new RegExp(entry))\n } catch (e) {\n logger.warn(`ip list entry \"${entry}\" is not a regular expression and matches nothing`)\n compiled.set(entry, null)\n }\n }\n return compiled.get(entry)\n}\n\n/** True when an entry of the list matches the address. A stored entry that does not compile matches nothing. */\nexport function ipListMatches(entries: unknown[], ip: string): boolean {\n return entries.some(entry => typeof entry === 'string' && !!patternOf(entry)?.test(ip))\n}\n\n/** Refuses the first entry, of whichever list, that is blank or not a regular expression - naming the list and the entry. */\nexport function refuseBadIPList(iplist: Record<string, unknown> | null | undefined): void {\n for (const list of IP_LISTS) {\n const entries = iplist?.[list]\n if (entries == null) continue\n if (!Array.isArray(entries)) {\n throw new Refusal(`ip-${list}-entry-invalid`, { entry: String(entries) }, `\"${entries}\" in the ${list} is not a regular expression`)\n }\n\n for (const entry of entries) {\n if (typeof entry === 'string' && !entry.trim()) {\n throw new Refusal(`ip-${list}-entry-empty`, {}, `the ${list} has an empty entry`)\n }\n try {\n if (typeof entry !== 'string') throw new TypeError()\n new RegExp(entry)\n } catch (e) {\n throw new Refusal(`ip-${list}-entry-invalid`, { entry: String(entry) }, `\"${entry}\" in the ${list} is not a regular expression`)\n }\n }\n }\n}\n"]}
|
|
@@ -9,6 +9,8 @@ const database_js_1 = require("../../initializers/database.js");
|
|
|
9
9
|
const domain_js_1 = require("./domain.js");
|
|
10
10
|
const domain_types_js_1 = require("./domain-types.js");
|
|
11
11
|
const scalar_object_js_1 = require("../common-types/scalar-object.js");
|
|
12
|
+
const ip_list_rule_js_1 = require("../../middlewares/ip-list-rule.js");
|
|
13
|
+
const refusal_js_1 = require("../../refusal/refusal.js");
|
|
12
14
|
let DomainMutation = class DomainMutation {
|
|
13
15
|
async createDomain(domainInput) {
|
|
14
16
|
const { name, description } = domainInput;
|
|
@@ -82,7 +84,7 @@ let DomainMutation = class DomainMutation {
|
|
|
82
84
|
async updateSecureIPList(iplist, context) {
|
|
83
85
|
const { domain } = context.state;
|
|
84
86
|
const repository = (0, database_js_1.getRepository)(domain_js_1.Domain);
|
|
85
|
-
|
|
87
|
+
(0, ip_list_rule_js_1.refuseBadIPList)(iplist);
|
|
86
88
|
const { iplist: result } = await repository.save({
|
|
87
89
|
...domain,
|
|
88
90
|
iplist
|
|
@@ -151,6 +153,7 @@ tslib_1.__decorate([
|
|
|
151
153
|
(0, type_graphql_1.Directive)('@transaction'),
|
|
152
154
|
(0, type_graphql_1.Directive)('@privilege(category: "security", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
153
155
|
(0, type_graphql_1.Mutation)(returns => scalar_object_js_1.ScalarObject, { nullable: true, description: 'To update secure IP list for domain' }),
|
|
156
|
+
(0, type_graphql_1.UseMiddleware)(refusal_js_1.SpeakRefusals),
|
|
154
157
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('iplist', type => scalar_object_js_1.ScalarObject)),
|
|
155
158
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
156
159
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain-mutation.js","sourceRoot":"","sources":["../../../server/service/domain/domain-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, Mutation, Query, Resolver, Root } from 'type-graphql'\nimport { In, Repository } from 'typeorm'\n\nimport { slugger } from '@things-factory/utils'\n\nimport { getRepository } from '../../initializers/database.js'\n\nimport { Domain, IPList } from './domain.js'\nimport { DomainPatch } from './domain-types.js'\nimport { ScalarObject } from '../common-types/scalar-object.js'\n\n@Resolver(Domain)\nexport class DomainMutation {\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description:\n 'Creates a new domain entity. Only superusers are granted this privilege. Use this mutation to register a new domain with a unique subdomain.'\n })\n async createDomain(\n @Arg('domainInput', { description: 'The input object containing the new domain details.' }) domainInput: DomainPatch\n ) {\n const { name, description } = domainInput\n const domainRepo: Repository<Domain> = getRepository(Domain)\n const subdomain: string = slugger(name)\n\n const domain: Domain = await domainRepo.findOneBy({ subdomain })\n if (domain) {\n throw new Error('domain is duplicated')\n }\n\n return await domainRepo.save({ name, description, subdomain })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description:\n 'Deletes a domain entity by its name. Only superusers are granted this privilege. Use this mutation to permanently remove a domain.'\n })\n async deleteDomain(@Arg('name', { description: 'The name of the domain to delete.' }) name: string) {\n return await getRepository(Domain).delete({ name })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description:\n 'Deletes multiple domain entities by their names. Only superusers are granted this privilege. Use this mutation to remove several domains at once.'\n })\n async deleteDomains(\n @Arg('names', () => [String], { description: 'An array of domain names to delete.' }) names: string[]\n ) {\n const domains: Domain[] = await getRepository(Domain).find({ where: { name: In(names) } })\n const domainIds: string[] = domains.map(domain => domain.id)\n\n await getRepository(Domain).delete({ id: In(domainIds) })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description: 'To update domain (Only superuser is granted this privilege.)'\n })\n async updateDomain(@Arg('name') name: string, @Arg('patch', () => DomainPatch) patch: DomainPatch) {\n const repository = getRepository(Domain)\n const domain: Domain = await repository.findOneBy({ name })\n\n if (patch.parent && patch.parent.id == domain.id) {\n delete patch.parent\n }\n\n return await repository.save({\n ...domain,\n ...patch\n } as any)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'To update multiple domains (Only superuser is granted this privilege.)'\n })\n async updateDomains(@Arg('patches', () => [DomainPatch]) patches: DomainPatch[]): Promise<boolean> {\n const domainRepo: Repository<Domain> = getRepository(Domain)\n\n const patchIds = patches.filter((patch: any) => patch.id)\n\n if (patchIds.length > 0) {\n /* 모든 저장을 await 해야 @transaction 스코프 안에서 커밋되고 에러가 호출자에 전파된다. */\n await Promise.all(\n patchIds.map(async updateRecord => {\n const domain: Domain = await domainRepo.findOne({ where: { id: updateRecord.id } })\n\n // 1. 도메인명(name)만 변경할 때는 slugger로 subdomain을 덮어쓰지 않는다 (기존 subdomain 유지).\n // 2. subdomain이 명시적으로 패치에 포함된 경우에만 slugger를 적용하고 중복 검사를 수행한다.\n if (updateRecord.subdomain) {\n updateRecord.subdomain = slugger(updateRecord.subdomain)\n if (updateRecord.subdomain !== domain.subdomain) {\n // 'system' 도메인의 서브도메인은 절대 변경할 수 없다.\n if (domain.subdomain === 'system' || domain.systemFlag) {\n throw new Error(\"The 'system' subdomain cannot be modified.\")\n }\n // 'system' 서브도메인은 예약어이므로 다른 도메인이 차지할 수 없다.\n if (updateRecord.subdomain === 'system') {\n throw new Error(\"The subdomain 'system' is reserved and cannot be used.\")\n }\n const dup = await domainRepo.findOneBy({ subdomain: updateRecord.subdomain })\n if (dup && dup.id !== domain.id) {\n throw new Error(`Subdomain '${updateRecord.subdomain}' is already in use.`)\n }\n }\n } else {\n delete updateRecord.subdomain\n }\n\n if (updateRecord.parent && updateRecord.parent.id == domain.id) {\n delete updateRecord.parent\n }\n\n await domainRepo.save({\n ...domain,\n ...updateRecord\n } as any)\n })\n )\n }\n\n return true\n }\n\n @Directive('@transaction')\n @Directive(\n '@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)'\n )\n @Mutation(returns => ScalarObject, { nullable: true, description: 'To update secure IP list for domain' })\n async updateSecureIPList(\n @Arg('iplist', type => ScalarObject) iplist: IPList,\n @Ctx() context: any\n ): Promise<IPList | null> {\n const { domain } = context.state\n const repository = getRepository(Domain)\n // const domain: Domain = await repository.findOneBy({ id })\n\n const { iplist: result } = await repository.save({\n ...domain,\n iplist\n } as any)\n\n return result\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"domain-mutation.js","sourceRoot":"","sources":["../../../server/service/domain/domain-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAwG;AACxG,qCAAwC;AAExC,iDAA+C;AAE/C,gEAA8D;AAE9D,2CAA4C;AAC5C,uDAA+C;AAC/C,uEAA+D;AAC/D,uEAAmE;AACnE,yDAAwD;AAGjD,IAAM,cAAc,GAApB,MAAM,cAAc;IAOnB,AAAN,KAAK,CAAC,YAAY,CAC4E,WAAwB;QAEpH,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,CAAA;QACzC,MAAM,UAAU,GAAuB,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAA;QAC5D,MAAM,SAAS,GAAW,IAAA,eAAO,EAAC,IAAI,CAAC,CAAA;QAEvC,MAAM,MAAM,GAAW,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;QAChE,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACzC,CAAC;QAED,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;IAChE,CAAC;IAQK,AAAN,KAAK,CAAC,YAAY,CAAoE,IAAY;QAChG,OAAO,MAAM,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,CAAC;IAQK,AAAN,KAAK,CAAC,aAAa,CACqE,KAAe;QAErG,MAAM,OAAO,GAAa,MAAM,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAA,YAAE,EAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;QAC1F,MAAM,SAAS,GAAa,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAE5D,MAAM,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IAC3D,CAAC;IAOK,AAAN,KAAK,CAAC,YAAY,CAAc,IAAY,EAAmC,KAAkB;QAC/F,MAAM,UAAU,GAAG,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAA;QACxC,MAAM,MAAM,GAAW,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;QAE3D,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACjD,OAAO,KAAK,CAAC,MAAM,CAAA;QACrB,CAAC;QAED,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC;YAC3B,GAAG,MAAM;YACT,GAAG,KAAK;SACF,CAAC,CAAA;IACX,CAAC;IAOK,AAAN,KAAK,CAAC,aAAa,CAAsC,OAAsB;QAC7E,MAAM,UAAU,GAAuB,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAA;QAE5D,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAEzD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,8DAA8D;YAC9D,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAC,YAAY,EAAC,EAAE;gBAChC,MAAM,MAAM,GAAW,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;gBAEnF,wEAAwE;gBACxE,8DAA8D;gBAC9D,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;oBAC3B,YAAY,CAAC,SAAS,GAAG,IAAA,eAAO,EAAC,YAAY,CAAC,SAAS,CAAC,CAAA;oBACxD,IAAI,YAAY,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;wBAChD,oCAAoC;wBACpC,IAAI,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;4BACvD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;wBAC/D,CAAC;wBACD,2CAA2C;wBAC3C,IAAI,YAAY,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;4BACxC,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;wBAC3E,CAAC;wBACD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAA;wBAC7E,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;4BAChC,MAAM,IAAI,KAAK,CAAC,cAAc,YAAY,CAAC,SAAS,sBAAsB,CAAC,CAAA;wBAC7E,CAAC;oBACH,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,OAAO,YAAY,CAAC,SAAS,CAAA;gBAC/B,CAAC;gBAED,IAAI,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;oBAC/D,OAAO,YAAY,CAAC,MAAM,CAAA;gBAC5B,CAAC;gBAED,MAAM,UAAU,CAAC,IAAI,CAAC;oBACpB,GAAG,MAAM;oBACT,GAAG,YAAY;iBACT,CAAC,CAAA;YACX,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAQK,AAAN,KAAK,CAAC,kBAAkB,CACe,MAAc,EAC5C,OAAY;QAEnB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAChC,MAAM,UAAU,GAAG,IAAA,2BAAa,EAAC,kBAAM,CAAC,CAAA;QAExC,IAAA,iCAAe,EAAC,MAAM,CAAC,CAAA;QAEvB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC;YAC/C,GAAG,MAAM;YACT,MAAM;SACA,CAAC,CAAA;QAET,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA;AA7IY,wCAAc;AAOnB;IANL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,kBAAM,EAAE;QAC3B,WAAW,EACT,8IAA8I;KACjJ,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,aAAa,EAAE,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC,CAAA;;6CAAc,6BAAW;;kDAYrH;AAQK;IANL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,kBAAM,EAAE;QAC3B,WAAW,EACT,oIAAoI;KACvI,CAAC;IACkB,mBAAA,IAAA,kBAAG,EAAC,MAAM,EAAE,EAAE,WAAW,EAAE,mCAAmC,EAAE,CAAC,CAAA;;;;kDAEpF;AAQK;IANL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAC5B,WAAW,EACT,mJAAmJ;KACtJ,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC,CAAA;;;;mDAMtF;AAOK;IALL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,kBAAM,EAAE;QAC3B,WAAW,EAAE,8DAA8D;KAC5E,CAAC;IACkB,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IAAgB,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,6BAAW,CAAC,CAAA;;qDAAQ,6BAAW;;kDAYhG;AAOK;IALL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAC5B,WAAW,EAAE,wEAAwE;KACtF,CAAC;IACmB,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,6BAAW,CAAC,CAAC,CAAA;;;;mDA8CvD;AAQK;IANL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EACR,2GAA2G,CAC5G;IACA,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,+BAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;IACzG,IAAA,4BAAa,EAAC,0BAAa,CAAC;IAE1B,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,+BAAY,CAAC,CAAA;IACnC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;wDAaP;yBA5IU,cAAc;IAD1B,IAAA,uBAAQ,EAAC,kBAAM,CAAC;GACJ,cAAc,CA6I1B","sourcesContent":["import { Arg, Args, Ctx, Directive, Mutation, Query, Resolver, Root, UseMiddleware } from 'type-graphql'\nimport { In, Repository } from 'typeorm'\n\nimport { slugger } from '@things-factory/utils'\n\nimport { getRepository } from '../../initializers/database.js'\n\nimport { Domain, IPList } from './domain.js'\nimport { DomainPatch } from './domain-types.js'\nimport { ScalarObject } from '../common-types/scalar-object.js'\nimport { refuseBadIPList } from '../../middlewares/ip-list-rule.js'\nimport { SpeakRefusals } from '../../refusal/refusal.js'\n\n@Resolver(Domain)\nexport class DomainMutation {\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description:\n 'Creates a new domain entity. Only superusers are granted this privilege. Use this mutation to register a new domain with a unique subdomain.'\n })\n async createDomain(\n @Arg('domainInput', { description: 'The input object containing the new domain details.' }) domainInput: DomainPatch\n ) {\n const { name, description } = domainInput\n const domainRepo: Repository<Domain> = getRepository(Domain)\n const subdomain: string = slugger(name)\n\n const domain: Domain = await domainRepo.findOneBy({ subdomain })\n if (domain) {\n throw new Error('domain is duplicated')\n }\n\n return await domainRepo.save({ name, description, subdomain })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description:\n 'Deletes a domain entity by its name. Only superusers are granted this privilege. Use this mutation to permanently remove a domain.'\n })\n async deleteDomain(@Arg('name', { description: 'The name of the domain to delete.' }) name: string) {\n return await getRepository(Domain).delete({ name })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description:\n 'Deletes multiple domain entities by their names. Only superusers are granted this privilege. Use this mutation to remove several domains at once.'\n })\n async deleteDomains(\n @Arg('names', () => [String], { description: 'An array of domain names to delete.' }) names: string[]\n ) {\n const domains: Domain[] = await getRepository(Domain).find({ where: { name: In(names) } })\n const domainIds: string[] = domains.map(domain => domain.id)\n\n await getRepository(Domain).delete({ id: In(domainIds) })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Domain, {\n description: 'To update domain (Only superuser is granted this privilege.)'\n })\n async updateDomain(@Arg('name') name: string, @Arg('patch', () => DomainPatch) patch: DomainPatch) {\n const repository = getRepository(Domain)\n const domain: Domain = await repository.findOneBy({ name })\n\n if (patch.parent && patch.parent.id == domain.id) {\n delete patch.parent\n }\n\n return await repository.save({\n ...domain,\n ...patch\n } as any)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'To update multiple domains (Only superuser is granted this privilege.)'\n })\n async updateDomains(@Arg('patches', () => [DomainPatch]) patches: DomainPatch[]): Promise<boolean> {\n const domainRepo: Repository<Domain> = getRepository(Domain)\n\n const patchIds = patches.filter((patch: any) => patch.id)\n\n if (patchIds.length > 0) {\n /* 모든 저장을 await 해야 @transaction 스코프 안에서 커밋되고 에러가 호출자에 전파된다. */\n await Promise.all(\n patchIds.map(async updateRecord => {\n const domain: Domain = await domainRepo.findOne({ where: { id: updateRecord.id } })\n\n // 1. 도메인명(name)만 변경할 때는 slugger로 subdomain을 덮어쓰지 않는다 (기존 subdomain 유지).\n // 2. subdomain이 명시적으로 패치에 포함된 경우에만 slugger를 적용하고 중복 검사를 수행한다.\n if (updateRecord.subdomain) {\n updateRecord.subdomain = slugger(updateRecord.subdomain)\n if (updateRecord.subdomain !== domain.subdomain) {\n // 'system' 도메인의 서브도메인은 절대 변경할 수 없다.\n if (domain.subdomain === 'system' || domain.systemFlag) {\n throw new Error(\"The 'system' subdomain cannot be modified.\")\n }\n // 'system' 서브도메인은 예약어이므로 다른 도메인이 차지할 수 없다.\n if (updateRecord.subdomain === 'system') {\n throw new Error(\"The subdomain 'system' is reserved and cannot be used.\")\n }\n const dup = await domainRepo.findOneBy({ subdomain: updateRecord.subdomain })\n if (dup && dup.id !== domain.id) {\n throw new Error(`Subdomain '${updateRecord.subdomain}' is already in use.`)\n }\n }\n } else {\n delete updateRecord.subdomain\n }\n\n if (updateRecord.parent && updateRecord.parent.id == domain.id) {\n delete updateRecord.parent\n }\n\n await domainRepo.save({\n ...domain,\n ...updateRecord\n } as any)\n })\n )\n }\n\n return true\n }\n\n @Directive('@transaction')\n @Directive(\n '@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)'\n )\n @Mutation(returns => ScalarObject, { nullable: true, description: 'To update secure IP list for domain' })\n @UseMiddleware(SpeakRefusals)\n async updateSecureIPList(\n @Arg('iplist', type => ScalarObject) iplist: IPList,\n @Ctx() context: any\n ): Promise<IPList | null> {\n const { domain } = context.state\n const repository = getRepository(Domain)\n\n refuseBadIPList(iplist)\n\n const { iplist: result } = await repository.save({\n ...domain,\n iplist\n } as any)\n\n return result\n }\n}\n"]}
|