@things-factory/shell 10.1.228 → 10.1.245

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.
@@ -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 = Array.isArray(whitelist) &&
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 = protectedlist.some(item => {
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":";;;AAqBA,4CAuDC;;AA5ED,oEAAkC;AAClC,0DAAyD;AAEzD;;;;;;;;;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,GACf,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;gBACxB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACpB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAClC,CAAC,CAAC,CAAA;YAEJ,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,eAAe,CAAC,OAAO,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,GACf,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;gBACxB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACpB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAClC,CAAC,CAAC,CAAA;YAEJ,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,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACrC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAClC,CAAC,CAAC,CAAA;YAEF,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'\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 =\n Array.isArray(whitelist) &&\n whitelist.some(item => {\n return new RegExp(item).test(ip)\n })\n\n if (!whitelisted) {\n return refuseByAddress(context)\n }\n } else {\n const blacklisted =\n Array.isArray(blacklist) &&\n blacklist.some(item => {\n return new RegExp(item).test(ip)\n })\n\n if (blacklisted) {\n return refuseByAddress(context)\n }\n }\n\n if (Array.isArray(protectedlist) && protectedlist.length > 0) {\n const safe = protectedlist.some(item => {\n return new RegExp(item).test(ip)\n })\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"]}
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"]}
@@ -25,3 +25,7 @@ export declare class ListParam {
25
25
  sortings?: Sorting[];
26
26
  inherited?: InheritedValueType;
27
27
  }
28
+ export declare class ListValueCount {
29
+ value: string;
30
+ count: number;
31
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ListParam = exports.Filter = exports.Pagination = exports.Sorting = exports.InheritedValueType = void 0;
3
+ exports.ListValueCount = exports.ListParam = exports.Filter = exports.Pagination = exports.Sorting = exports.InheritedValueType = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const scalar_any_js_1 = require("./scalar-any.js");
@@ -94,4 +94,18 @@ tslib_1.__decorate([
94
94
  exports.ListParam = ListParam = tslib_1.__decorate([
95
95
  (0, type_graphql_1.ArgsType)()
96
96
  ], ListParam);
97
+ let ListValueCount = class ListValueCount {
98
+ };
99
+ exports.ListValueCount = ListValueCount;
100
+ tslib_1.__decorate([
101
+ (0, type_graphql_1.Field)({ description: 'The value of the field.' }),
102
+ tslib_1.__metadata("design:type", String)
103
+ ], ListValueCount.prototype, "value", void 0);
104
+ tslib_1.__decorate([
105
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Rows holding it, under the list conditions other than those on the field.' }),
106
+ tslib_1.__metadata("design:type", Number)
107
+ ], ListValueCount.prototype, "count", void 0);
108
+ exports.ListValueCount = ListValueCount = tslib_1.__decorate([
109
+ (0, type_graphql_1.ObjectType)({ description: "How many rows of a list hold one value of a field, under the list's other conditions - the number beside a filter chip." })
110
+ ], ListValueCount);
97
111
  //# sourceMappingURL=list-param.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"list-param.js","sourceRoot":"","sources":["../../../server/service/common-types/list-param.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArgsType, Field, InputType, Int, registerEnumType } from 'type-graphql'\n\nimport { ScalarAny } from './scalar-any.js'\n\nexport enum InheritedValueType {\n None = 'None',\n Only = 'Only',\n Include = 'Include'\n}\n\nregisterEnumType(InheritedValueType, {\n name: 'InheritedValueType',\n description:\n 'Enumeration for inherited value types: None, Only, or Include. Used to specify how values are inherited in queries or filters.'\n})\n\n@InputType({ description: 'Input type for specifying sorting options in a list query.' })\nexport class Sorting {\n @Field({ description: 'The field name to sort the results by.' })\n name: string\n\n @Field({ nullable: true, description: 'Set to true for descending order. Default is false (ascending).' })\n desc?: boolean\n}\n\n@InputType({ description: 'Input type for specifying pagination options in a list query.' })\nexport class Pagination {\n @Field(type => Int, { nullable: true, description: 'The page number to retrieve.' })\n page?: number\n\n @Field(type => Int, { nullable: true, description: 'The maximum number of items per page.' })\n limit?: number\n\n /*\n * 커서(keyset) 페이징 — `page` 대신 \"이 행 다음부터\" 를 준다.\n *\n * ── 왜 필요한가 (성능보다 먼저, 정확성) ─────────────────────────────────────\n * offset 페이징은 **목록이 정지해 있다고 가정한다.** 앞에 행이 새로 쌓이는 목록(이벤트 저널,\n * 알림, 로그, 최신순 게시물)에서는 1페이지를 읽는 사이 들어온 행 수만큼 뒤 페이지가 밀린다 →\n * **이미 본 행이 또 나오거나, 못 본 행이 통째로 건너뛰어진다.** 실시간 목록에서 offset 은\n * 느린 게 아니라 틀린다.\n * 덤으로 `OFFSET 900000` 은 DB 가 앞의 90만 행을 세고 버리므로 깊은 페이지에서 급격히 느려진다.\n *\n * `page` 는 그대로 둔다(하위호환). 둘 다 주면 `after` 가 이긴다.\n */\n @Field({ nullable: true, description: 'Keyset cursor: return rows positioned after this one, per the current sorting. Prefer this over page for feeds that grow at the head (event journals, logs, newest-first lists) where offset paging silently duplicates or skips rows as new rows arrive. Opaque — pass back the nextCursor the previous response returned. Wins over page when both are given.' })\n after?: string\n}\n\n@InputType({ description: 'Input type for specifying a filter condition in a list query.' })\nexport class Filter {\n @Field({ description: 'The field name to apply the filter on.' })\n name: string\n\n @Field({ description: 'The operator to use for filtering (e.g., eq, ne, gt, lt, in, etc.).' })\n operator: string\n\n @Field(type => ScalarAny, { nullable: true, description: 'The value to compare the field against.' })\n value?: any\n\n @Field({ nullable: true, description: 'Set to true if the filter applies to a relation field.' })\n relation?: boolean\n}\n\n@ArgsType()\nexport class ListParam {\n @Field(type => [Filter], { nullable: true, description: 'An array of filter conditions to apply to the list query.' })\n filters?: Filter[]\n\n /*\n * 전역 검색어 — 한 낱말로 여러 축을 한 번에 훑는다.\n *\n * 예전에는 이걸 표현할 자리가 없어서, 소비처마다 `searchables` 와 같은 필드 목록을 손으로 알아내\n * 필드 수만큼 `{name, operator:'search', value}` 를 조립해 보냈다(같은 조립이 수백 곳에 복제됐다).\n * 게다가 그 목록이 서버의 `searchables` 와 어긋나면 조용히 무시됐다 — 화면은 걸린 줄 아는데 안 걸린다.\n *\n * 이 필드를 주면 **서버가 자기 `searchables` 로** OR 를 만든다. 클라이언트는 무엇으로 찾을 수 있는지\n * 알 필요가 없다. 기존 `filters` 방식은 그대로 동작한다(둘을 함께 줘도 AND 로 합쳐진다).\n */\n @Field({ nullable: true, description: 'A single free-text term matched across the fields the resolver declares as searchable (OR). Additive to filters: both are applied together. Prefer this over hand-assembling one search filter per field.' })\n search?: string\n\n @Field(type => Pagination, { nullable: true, description: 'Pagination options for the list query.' })\n pagination?: Pagination\n\n @Field(type => [Sorting], { nullable: true, description: 'Sorting options for the list query.' })\n sortings?: Sorting[]\n\n @Field(type => InheritedValueType, { nullable: true, description: 'Inherited value type for the list query.' })\n inherited?: InheritedValueType\n}\n"]}
1
+ {"version":3,"file":"list-param.js","sourceRoot":"","sources":["../../../server/service/common-types/list-param.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArgsType, Field, InputType, Int, ObjectType, registerEnumType } from 'type-graphql'\n\nimport { ScalarAny } from './scalar-any.js'\n\nexport enum InheritedValueType {\n None = 'None',\n Only = 'Only',\n Include = 'Include'\n}\n\nregisterEnumType(InheritedValueType, {\n name: 'InheritedValueType',\n description:\n 'Enumeration for inherited value types: None, Only, or Include. Used to specify how values are inherited in queries or filters.'\n})\n\n@InputType({ description: 'Input type for specifying sorting options in a list query.' })\nexport class Sorting {\n @Field({ description: 'The field name to sort the results by.' })\n name: string\n\n @Field({ nullable: true, description: 'Set to true for descending order. Default is false (ascending).' })\n desc?: boolean\n}\n\n@InputType({ description: 'Input type for specifying pagination options in a list query.' })\nexport class Pagination {\n @Field(type => Int, { nullable: true, description: 'The page number to retrieve.' })\n page?: number\n\n @Field(type => Int, { nullable: true, description: 'The maximum number of items per page.' })\n limit?: number\n\n /*\n * 커서(keyset) 페이징 — `page` 대신 \"이 행 다음부터\" 를 준다.\n *\n * ── 왜 필요한가 (성능보다 먼저, 정확성) ─────────────────────────────────────\n * offset 페이징은 **목록이 정지해 있다고 가정한다.** 앞에 행이 새로 쌓이는 목록(이벤트 저널,\n * 알림, 로그, 최신순 게시물)에서는 1페이지를 읽는 사이 들어온 행 수만큼 뒤 페이지가 밀린다 →\n * **이미 본 행이 또 나오거나, 못 본 행이 통째로 건너뛰어진다.** 실시간 목록에서 offset 은\n * 느린 게 아니라 틀린다.\n * 덤으로 `OFFSET 900000` 은 DB 가 앞의 90만 행을 세고 버리므로 깊은 페이지에서 급격히 느려진다.\n *\n * `page` 는 그대로 둔다(하위호환). 둘 다 주면 `after` 가 이긴다.\n */\n @Field({ nullable: true, description: 'Keyset cursor: return rows positioned after this one, per the current sorting. Prefer this over page for feeds that grow at the head (event journals, logs, newest-first lists) where offset paging silently duplicates or skips rows as new rows arrive. Opaque — pass back the nextCursor the previous response returned. Wins over page when both are given.' })\n after?: string\n}\n\n@InputType({ description: 'Input type for specifying a filter condition in a list query.' })\nexport class Filter {\n @Field({ description: 'The field name to apply the filter on.' })\n name: string\n\n @Field({ description: 'The operator to use for filtering (e.g., eq, ne, gt, lt, in, etc.).' })\n operator: string\n\n @Field(type => ScalarAny, { nullable: true, description: 'The value to compare the field against.' })\n value?: any\n\n @Field({ nullable: true, description: 'Set to true if the filter applies to a relation field.' })\n relation?: boolean\n}\n\n@ArgsType()\nexport class ListParam {\n @Field(type => [Filter], { nullable: true, description: 'An array of filter conditions to apply to the list query.' })\n filters?: Filter[]\n\n /*\n * 전역 검색어 — 한 낱말로 여러 축을 한 번에 훑는다.\n *\n * 예전에는 이걸 표현할 자리가 없어서, 소비처마다 `searchables` 와 같은 필드 목록을 손으로 알아내\n * 필드 수만큼 `{name, operator:'search', value}` 를 조립해 보냈다(같은 조립이 수백 곳에 복제됐다).\n * 게다가 그 목록이 서버의 `searchables` 와 어긋나면 조용히 무시됐다 — 화면은 걸린 줄 아는데 안 걸린다.\n *\n * 이 필드를 주면 **서버가 자기 `searchables` 로** OR 를 만든다. 클라이언트는 무엇으로 찾을 수 있는지\n * 알 필요가 없다. 기존 `filters` 방식은 그대로 동작한다(둘을 함께 줘도 AND 로 합쳐진다).\n */\n @Field({ nullable: true, description: 'A single free-text term matched across the fields the resolver declares as searchable (OR). Additive to filters: both are applied together. Prefer this over hand-assembling one search filter per field.' })\n search?: string\n\n @Field(type => Pagination, { nullable: true, description: 'Pagination options for the list query.' })\n pagination?: Pagination\n\n @Field(type => [Sorting], { nullable: true, description: 'Sorting options for the list query.' })\n sortings?: Sorting[]\n\n @Field(type => InheritedValueType, { nullable: true, description: 'Inherited value type for the list query.' })\n inherited?: InheritedValueType\n}\n\n@ObjectType({ description: \"How many rows of a list hold one value of a field, under the list's other conditions - the number beside a filter chip.\" })\nexport class ListValueCount {\n @Field({ description: 'The value of the field.' })\n value: string\n\n @Field(type => Int, { description: 'Rows holding it, under the list conditions other than those on the field.' })\n count: number\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
- // const domain: Domain = await repository.findOneBy({ id })
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":"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","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"]}