@things-factory/auth-base 10.1.31 → 10.1.33

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.
Files changed (108) hide show
  1. package/dist-server/controllers/invitation.d.ts +47 -10
  2. package/dist-server/controllers/invitation.js +159 -113
  3. package/dist-server/controllers/invitation.js.map +1 -1
  4. package/dist-server/controllers/profile.d.ts +1 -0
  5. package/dist-server/router/auth-public-process-router.js +36 -13
  6. package/dist-server/router/auth-public-process-router.js.map +1 -1
  7. package/dist-server/router/oauth2/oauth2-router.js +7 -2
  8. package/dist-server/router/oauth2/oauth2-router.js.map +1 -1
  9. package/dist-server/router/oauth2/oauth2-server.js +4 -4
  10. package/dist-server/router/oauth2/oauth2-server.js.map +1 -1
  11. package/dist-server/service/app-binding/app-binding-mutation.d.ts +27 -0
  12. package/dist-server/service/app-binding/app-binding-mutation.js +39 -6
  13. package/dist-server/service/app-binding/app-binding-mutation.js.map +1 -1
  14. package/dist-server/service/app-binding/app-binding-query.js +14 -7
  15. package/dist-server/service/app-binding/app-binding-query.js.map +1 -1
  16. package/dist-server/service/app-binding/app-binding.d.ts +9 -0
  17. package/dist-server/service/app-binding/app-binding.js +10 -1
  18. package/dist-server/service/app-binding/app-binding.js.map +1 -1
  19. package/dist-server/service/appliance/appliance-mutation.js +34 -1
  20. package/dist-server/service/appliance/appliance-mutation.js.map +1 -1
  21. package/dist-server/service/appliance/appliance-query.d.ts +2 -0
  22. package/dist-server/service/appliance/appliance-query.js +48 -0
  23. package/dist-server/service/appliance/appliance-query.js.map +1 -1
  24. package/dist-server/service/appliance/appliance.d.ts +1 -0
  25. package/dist-server/service/appliance/appliance.js +33 -3
  26. package/dist-server/service/appliance/appliance.js.map +1 -1
  27. package/dist-server/service/application/application-mutation.js +51 -6
  28. package/dist-server/service/application/application-mutation.js.map +1 -1
  29. package/dist-server/service/application/application.d.ts +9 -3
  30. package/dist-server/service/application/application.js +24 -8
  31. package/dist-server/service/application/application.js.map +1 -1
  32. package/dist-server/service/auth-provider/auth-provider-mutation.js +5 -0
  33. package/dist-server/service/auth-provider/auth-provider-mutation.js.map +1 -1
  34. package/dist-server/service/domain-generator/domain-generator-mutation.js +3 -0
  35. package/dist-server/service/domain-generator/domain-generator-mutation.js.map +1 -1
  36. package/dist-server/service/invitation/invitation-mutation.d.ts +17 -15
  37. package/dist-server/service/invitation/invitation-mutation.js +83 -56
  38. package/dist-server/service/invitation/invitation-mutation.js.map +1 -1
  39. package/dist-server/service/invitation/invitation-query.d.ts +20 -5
  40. package/dist-server/service/invitation/invitation-query.js +60 -19
  41. package/dist-server/service/invitation/invitation-query.js.map +1 -1
  42. package/dist-server/service/invitation/invitation.d.ts +14 -2
  43. package/dist-server/service/invitation/invitation.js +77 -8
  44. package/dist-server/service/invitation/invitation.js.map +1 -1
  45. package/dist-server/service/login-history/login-history-query.js +3 -0
  46. package/dist-server/service/login-history/login-history-query.js.map +1 -1
  47. package/dist-server/service/role/role-mutation.js +14 -15
  48. package/dist-server/service/role/role-mutation.js.map +1 -1
  49. package/dist-server/service/role/role-query.js +26 -4
  50. package/dist-server/service/role/role-query.js.map +1 -1
  51. package/dist-server/service/role/role-types.d.ts +2 -0
  52. package/dist-server/service/role/role-types.js +8 -0
  53. package/dist-server/service/role/role-types.js.map +1 -1
  54. package/dist-server/service/role-template/role-template-mutation.d.ts +17 -1
  55. package/dist-server/service/role-template/role-template-mutation.js +14 -3
  56. package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
  57. package/dist-server/service/user/user-mutation.js +1 -0
  58. package/dist-server/service/user/user-mutation.js.map +1 -1
  59. package/dist-server/service/user/user.d.ts +1 -0
  60. package/dist-server/service/user/user.js +19 -4
  61. package/dist-server/service/user/user.js.map +1 -1
  62. package/dist-server/templates/invitation-email.d.ts +2 -1
  63. package/dist-server/templates/invitation-email.js +16 -4
  64. package/dist-server/templates/invitation-email.js.map +1 -1
  65. package/dist-server/tsconfig.tsbuildinfo +1 -1
  66. package/dist-server/utils/credential-at-rest.d.ts +28 -0
  67. package/dist-server/utils/credential-at-rest.js +36 -0
  68. package/dist-server/utils/credential-at-rest.js.map +1 -0
  69. package/dist-server/utils/credential-features.d.ts +35 -0
  70. package/dist-server/utils/credential-features.js +45 -0
  71. package/dist-server/utils/credential-features.js.map +1 -0
  72. package/dist-server/utils/credential-serial-rule.d.ts +41 -0
  73. package/dist-server/utils/credential-serial-rule.js +43 -0
  74. package/dist-server/utils/credential-serial-rule.js.map +1 -0
  75. package/dist-server/utils/invitation-state.d.ts +52 -0
  76. package/dist-server/utils/invitation-state.js +76 -0
  77. package/dist-server/utils/invitation-state.js.map +1 -0
  78. package/dist-server/utils/refuse-role-name.d.ts +42 -0
  79. package/dist-server/utils/refuse-role-name.js +76 -0
  80. package/dist-server/utils/refuse-role-name.js.map +1 -0
  81. package/dist-server/utils/role-name-standing.d.ts +58 -0
  82. package/dist-server/utils/role-name-standing.js +72 -0
  83. package/dist-server/utils/role-name-standing.js.map +1 -0
  84. package/package.json +4 -4
  85. package/tests/app-binding-delete-db.test.ts +188 -0
  86. package/tests/appliance-credential-state-db.test.ts +207 -0
  87. package/tests/appliance-delete-db.test.ts +119 -0
  88. package/tests/appliance-revoke-db.test.ts +73 -28
  89. package/tests/appliance-schema.test.ts +129 -0
  90. package/tests/application-delete-db.test.ts +190 -0
  91. package/tests/application-token-payload-db.test.ts +185 -0
  92. package/tests/checkin-privilege-seam-db.test.ts +158 -0
  93. package/tests/credential-at-rest-db.test.ts +246 -0
  94. package/tests/credential-features-off-db.test.ts +189 -0
  95. package/tests/credential-serial-rule.test.ts +84 -0
  96. package/tests/invitation-db.test.ts +416 -0
  97. package/tests/invitation-state.test.ts +92 -0
  98. package/tests/open-doors.test.ts +132 -0
  99. package/tests/role-inheritance-db.test.ts +286 -0
  100. package/tests/role-mutation-db.test.ts +21 -7
  101. package/tests/role-name-lookup-sentinel.test.ts +79 -0
  102. package/tests/role-name-standing.test.ts +83 -0
  103. package/tests/token-issuance.test.ts +7 -3
  104. package/translations/en.json +2 -0
  105. package/translations/ja.json +2 -0
  106. package/translations/ko.json +2 -0
  107. package/translations/ms.json +2 -0
  108. package/translations/zh.json +2 -0
@@ -4,6 +4,7 @@ exports.ApplianceQuery = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const shell_1 = require("@things-factory/shell");
7
+ const credential_features_js_1 = require("../../utils/credential-features.js");
7
8
  const appliance_js_1 = require("../appliance/appliance.js");
8
9
  const user_js_1 = require("../user/user.js");
9
10
  const appliance_types_js_1 = require("./appliance-types.js");
@@ -36,9 +37,42 @@ let ApplianceQuery = class ApplianceQuery {
36
37
  const [items, total] = await queryBuilder.getManyAndCount();
37
38
  return { items, total };
38
39
  }
40
+ /*
41
+ * Does revoking an appliance credential work here? (ADR-0085 decision 1.)
42
+ *
43
+ * ── Why the screen has to ask ───────────────────────────────────────────
44
+ * Revocation is behind `credential/revocable`, and an installation that never configured it
45
+ * has no such door — `revokeApplianceCredential` answers 404. The screen cannot read the
46
+ * config, so without this it would either hide a feature the installation paid for or show a
47
+ * button that always refuses. The second is the worse one: it reads as a broken product
48
+ * rather than as something nobody turned on.
49
+ *
50
+ * ── Why it carries the mutation's own gate ──────────────────────────────
51
+ * The same `category` / `privilege` pair as `revokeApplianceCredential`, so what comes back is
52
+ * not "the installation has this" but **"pressing it would work for you"**. Those differ for a
53
+ * reader who may list appliances and may not act on their credentials, and a button should
54
+ * follow the second. A refusal here reaches the client as an error, which it reads the same
55
+ * way as false.
56
+ */
57
+ async applianceCredentialRevocable() {
58
+ return (0, credential_features_js_1.offersCredentialRevocation)();
59
+ }
39
60
  async accessToken(appliance, context) {
40
61
  return appliance.accessToken;
41
62
  }
63
+ /*
64
+ * Revocation is recorded on the shadow user, so the answer is not on the row that was asked
65
+ * for. INACTIVE means revoked and nothing else does: `ensureApplianceUser` creates the user
66
+ * ACTIVATED, `generateApplianceSecret` puts it back to ACTIVATED, and `revokeApplianceCredential`
67
+ * is the one place that sets INACTIVE.
68
+ *
69
+ * An appliance with no shadow user at all is not revoked — it is one nothing was ever issued
70
+ * for, which is the state a freshly registered appliance is in.
71
+ */
72
+ async credentialRevoked(appliance) {
73
+ const appuser = await (0, shell_1.getRepository)(user_js_1.User).findOneBy({ reference: appliance.id, userType: 'appliance' });
74
+ return appuser?.status === user_js_1.UserStatus.INACTIVE;
75
+ }
42
76
  async domain(context) {
43
77
  return context.state.domain;
44
78
  }
@@ -77,6 +111,13 @@ tslib_1.__decorate([
77
111
  tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
78
112
  tslib_1.__metadata("design:returntype", Promise)
79
113
  ], ApplianceQuery.prototype, "edges", null);
114
+ tslib_1.__decorate([
115
+ (0, type_graphql_1.Directive)('@privilege(category: "security", privilege: "mutation", domainOwnerGranted: true)'),
116
+ (0, type_graphql_1.Query)(returns => Boolean, { description: 'Whether this installation offers appliance credential revocation' }),
117
+ tslib_1.__metadata("design:type", Function),
118
+ tslib_1.__metadata("design:paramtypes", []),
119
+ tslib_1.__metadata("design:returntype", Promise)
120
+ ], ApplianceQuery.prototype, "applianceCredentialRevocable", null);
80
121
  tslib_1.__decorate([
81
122
  (0, type_graphql_1.FieldResolver)(type => String),
82
123
  tslib_1.__param(0, (0, type_graphql_1.Root)()),
@@ -85,6 +126,13 @@ tslib_1.__decorate([
85
126
  tslib_1.__metadata("design:paramtypes", [appliance_js_1.Appliance, Object]),
86
127
  tslib_1.__metadata("design:returntype", Promise)
87
128
  ], ApplianceQuery.prototype, "accessToken", null);
129
+ tslib_1.__decorate([
130
+ (0, type_graphql_1.FieldResolver)(type => Boolean),
131
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
132
+ tslib_1.__metadata("design:type", Function),
133
+ tslib_1.__metadata("design:paramtypes", [appliance_js_1.Appliance]),
134
+ tslib_1.__metadata("design:returntype", Promise)
135
+ ], ApplianceQuery.prototype, "credentialRevoked", null);
88
136
  tslib_1.__decorate([
89
137
  (0, type_graphql_1.FieldResolver)(type => shell_1.Domain),
90
138
  tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
@@ -1 +1 @@
1
- {"version":3,"file":"appliance-query.js","sourceRoot":"","sources":["../../../server/service/appliance/appliance-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { getQueryBuilderFromListParams, Domain, getRepository, ListParam } from '@things-factory/shell'\n\nimport { Appliance } from '../appliance/appliance.js'\nimport { User } from '../user/user.js'\nimport { ApplianceList } from './appliance-types.js'\n\n@Resolver(Appliance)\nexport class ApplianceQuery {\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => Appliance, { description: ' To fetch appliance' })\n async appliance(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Appliance> {\n const { domain } = context.state\n return await getRepository(Appliance).findOneBy({ domain: { id: domain.id }, id })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ApplianceList, { description: 'To fetch multiple appliance' })\n async appliances(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ApplianceList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(Appliance),\n alias: 'appliance',\n searchables: ['name', 'description']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ApplianceList, { description: 'To fetch multiple appliance' })\n async edges(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<ApplianceList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(Appliance),\n alias: 'appliance',\n searchables: ['name', 'description']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => String)\n async accessToken(@Root() appliance: Appliance, @Ctx() context: ResolverContext) {\n return appliance.accessToken\n }\n\n @FieldResolver(type => Domain)\n async domain(@Ctx() context: ResolverContext) {\n return context.state.domain\n }\n\n @FieldResolver(type => User)\n async updater(@Root() appliance: Appliance): Promise<User> {\n return await getRepository(User).findOneBy({ id: appliance.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() appliance: Appliance): Promise<User> {\n return await getRepository(User).findOneBy({ id: appliance.creatorId })\n }\n}\n"]}
1
+ {"version":3,"file":"appliance-query.js","sourceRoot":"","sources":["../../../server/service/appliance/appliance-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { getQueryBuilderFromListParams, Domain, getRepository, ListParam } from '@things-factory/shell'\n\nimport { offersCredentialRevocation } from '../../utils/credential-features.js'\nimport { Appliance } from '../appliance/appliance.js'\nimport { User, UserStatus } from '../user/user.js'\nimport { ApplianceList } from './appliance-types.js'\n\n@Resolver(Appliance)\nexport class ApplianceQuery {\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => Appliance, { description: ' To fetch appliance' })\n async appliance(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Appliance> {\n const { domain } = context.state\n return await getRepository(Appliance).findOneBy({ domain: { id: domain.id }, id })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ApplianceList, { description: 'To fetch multiple appliance' })\n async appliances(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<ApplianceList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(Appliance),\n alias: 'appliance',\n searchables: ['name', 'description']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ApplianceList, { description: 'To fetch multiple appliance' })\n async edges(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<ApplianceList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: getRepository(Appliance),\n alias: 'appliance',\n searchables: ['name', 'description']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /*\n * Does revoking an appliance credential work here? (ADR-0085 decision 1.)\n *\n * ── Why the screen has to ask ───────────────────────────────────────────\n * Revocation is behind `credential/revocable`, and an installation that never configured it\n * has no such door — `revokeApplianceCredential` answers 404. The screen cannot read the\n * config, so without this it would either hide a feature the installation paid for or show a\n * button that always refuses. The second is the worse one: it reads as a broken product\n * rather than as something nobody turned on.\n *\n * ── Why it carries the mutation's own gate ──────────────────────────────\n * The same `category` / `privilege` pair as `revokeApplianceCredential`, so what comes back is\n * not \"the installation has this\" but **\"pressing it would work for you\"**. Those differ for a\n * reader who may list appliances and may not act on their credentials, and a button should\n * follow the second. A refusal here reaches the client as an error, which it reads the same\n * way as false.\n */\n @Directive('@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Query(returns => Boolean, { description: 'Whether this installation offers appliance credential revocation' })\n async applianceCredentialRevocable(): Promise<boolean> {\n return offersCredentialRevocation()\n }\n\n @FieldResolver(type => String)\n async accessToken(@Root() appliance: Appliance, @Ctx() context: ResolverContext) {\n return appliance.accessToken\n }\n\n /*\n * Revocation is recorded on the shadow user, so the answer is not on the row that was asked\n * for. INACTIVE means revoked and nothing else does: `ensureApplianceUser` creates the user\n * ACTIVATED, `generateApplianceSecret` puts it back to ACTIVATED, and `revokeApplianceCredential`\n * is the one place that sets INACTIVE.\n *\n * An appliance with no shadow user at all is not revoked — it is one nothing was ever issued\n * for, which is the state a freshly registered appliance is in.\n */\n @FieldResolver(type => Boolean)\n async credentialRevoked(@Root() appliance: Appliance): Promise<boolean> {\n const appuser = await getRepository(User).findOneBy({ reference: appliance.id, userType: 'appliance' })\n\n return appuser?.status === UserStatus.INACTIVE\n }\n\n @FieldResolver(type => Domain)\n async domain(@Ctx() context: ResolverContext) {\n return context.state.domain\n }\n\n @FieldResolver(type => User)\n async updater(@Root() appliance: Appliance): Promise<User> {\n return await getRepository(User).findOneBy({ id: appliance.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() appliance: Appliance): Promise<User> {\n return await getRepository(User).findOneBy({ id: appliance.creatorId })\n }\n}\n"]}
@@ -12,6 +12,7 @@ export declare class Appliance {
12
12
  netmask: string;
13
13
  description: string;
14
14
  accessToken: string;
15
+ credentialRevoked?: boolean;
15
16
  creator: User;
16
17
  creatorId: string;
17
18
  updater: User;
@@ -6,13 +6,23 @@ const typeorm_1 = require("typeorm");
6
6
  const type_graphql_1 = require("type-graphql");
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const env_1 = require("@things-factory/env");
9
+ const credential_at_rest_js_1 = require("../../utils/credential-at-rest.js");
9
10
  const user_js_1 = require("../user/user.js");
10
11
  const get_secret_js_1 = require("../../utils/get-secret.js");
11
12
  const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
12
13
  const ORMCONFIG = env_1.config.get('ormconfig', {});
13
14
  const DATABASE_TYPE = ORMCONFIG.type;
14
15
  let Appliance = class Appliance {
15
- /* signing for jsonwebtoken */
16
+ /*
17
+ * `cs` is the credential serial (ADR-0085 decision 3) — which generation of this appliance's
18
+ * credential the token belongs to. It is absent for a subject that has never been through the
19
+ * new path, and `checkAuth` lets an absent one by, which is what keeps the tokens already in
20
+ * the field working.
21
+ *
22
+ * It also makes each issue distinct. The rest of this payload is the same every time and
23
+ * `iat` counts whole seconds, so two signings inside one second used to produce the identical
24
+ * string — revoking and immediately re-issuing handed back the credential just revoked.
25
+ */
16
26
  static sign(subject, expiresIn, domain, user, appliance) {
17
27
  var credential = {
18
28
  id: user.id,
@@ -25,6 +35,9 @@ let Appliance = class Appliance {
25
35
  subdomain: domain.subdomain
26
36
  }
27
37
  };
38
+ if (user.credentialSerial !== null && user.credentialSerial !== undefined) {
39
+ credential.cs = user.credentialSerial;
40
+ }
28
41
  return jsonwebtoken_1.default.sign(credential, get_secret_js_1.SECRET, {
29
42
  expiresIn,
30
43
  issuer: 'hatiolab.com',
@@ -145,12 +158,20 @@ tslib_1.__decorate([
145
158
  : DATABASE_TYPE == 'mssql'
146
159
  ? 'nvarchar'
147
160
  : 'varchar',
148
- length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
161
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,
162
+ transformer: credential_at_rest_js_1.credentialAtRestTransformer
149
163
  }),
150
164
  (0, type_graphql_1.Field)({ nullable: true, description: 'The access token for the appliance.' }),
151
165
  (0, type_graphql_1.Directive)('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)'),
152
166
  tslib_1.__metadata("design:type", String)
153
167
  ], Appliance.prototype, "accessToken", void 0);
168
+ tslib_1.__decorate([
169
+ (0, type_graphql_1.Field)(type => Boolean, {
170
+ nullable: true,
171
+ description: "Whether this appliance's credential has been revoked."
172
+ }),
173
+ tslib_1.__metadata("design:type", Boolean)
174
+ ], Appliance.prototype, "credentialRevoked", void 0);
154
175
  tslib_1.__decorate([
155
176
  (0, typeorm_1.ManyToOne)(type => user_js_1.User, { nullable: true }),
156
177
  (0, type_graphql_1.Field)({ nullable: true, description: 'The user who created the appliance.' }),
@@ -178,7 +199,16 @@ tslib_1.__decorate([
178
199
  (0, typeorm_1.UpdateDateColumn)(),
179
200
  (0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the appliance was last updated.' }),
180
201
  tslib_1.__metadata("design:type", Date
181
- /* signing for jsonwebtoken */
202
+ /*
203
+ * `cs` is the credential serial (ADR-0085 decision 3) — which generation of this appliance's
204
+ * credential the token belongs to. It is absent for a subject that has never been through the
205
+ * new path, and `checkAuth` lets an absent one by, which is what keeps the tokens already in
206
+ * the field working.
207
+ *
208
+ * It also makes each issue distinct. The rest of this payload is the same every time and
209
+ * `iat` counts whole seconds, so two signings inside one second used to produce the identical
210
+ * string — revoking and immediately re-issuing handed back the credential just revoked.
211
+ */
182
212
  )
183
213
  ], Appliance.prototype, "updatedAt", void 0);
184
214
  exports.Appliance = Appliance = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"appliance.js","sourceRoot":"","sources":["../../../server/service/appliance/appliance.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n EntityManager,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Directive, Field, ID, ObjectType } from 'type-graphql'\nimport { Domain, getRepository } from '@things-factory/shell'\nimport { config } from '@things-factory/env'\n\nimport { User, UserStatus } from '../user/user.js'\nimport { SECRET } from '../../utils/get-secret.js'\nimport jwt from 'jsonwebtoken'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_appliance_0', (appliance: Appliance) => [appliance.domain, appliance.name], {\n unique: true\n})\n@ObjectType({ description: 'Represents a physical or virtual appliance that can be managed by the system.' })\nexport class Appliance {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the appliance.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain to which the appliance belongs.' })\n domain?: Domain\n\n @RelationId((appliance: Appliance) => appliance.domain)\n domainId: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'The serial number of the appliance.' })\n serialNo: string\n\n @Column()\n @Field({ description: 'The name of the appliance.' })\n name: string\n\n @Column()\n @Field({ nullable: true, description: 'The brand or manufacturer of the appliance.' })\n brand: string\n\n @Column()\n @Field({ nullable: true, description: 'The model of the appliance.' })\n model: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'The network mask of the appliance.' })\n netmask: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'A description of the appliance.' })\n description: string\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'The access token for the appliance.' })\n @Directive('@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)')\n accessToken: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who created the appliance.' })\n creator: User\n\n @RelationId((appliance: Appliance) => appliance.creator)\n creatorId: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who last updated the appliance.' })\n updater: User\n\n @RelationId((appliance: Appliance) => appliance.updater)\n updaterId: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the appliance was created.' })\n createdAt: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the appliance was last updated.' })\n updatedAt: Date\n\n /* signing for jsonwebtoken */\n static sign(subject, expiresIn, domain, user, appliance) {\n var credential = {\n id: user.id,\n userType: 'appliance',\n appliance: {\n id: appliance.id\n },\n status: UserStatus.ACTIVATED,\n domain: {\n subdomain: domain.subdomain\n }\n }\n\n return jwt.sign(credential, SECRET, {\n expiresIn,\n issuer: 'hatiolab.com',\n subject\n })\n }\n\n static generateAccessToken(domain, user, appliance) {\n /* how to set expiresIn https://github.com/vercel/ms */\n let expiresIn = config.get('applianceJwtExpiresIn', '1y')\n return this.sign('access-token', expiresIn, domain, user, appliance)\n }\n\n static async ensureApplianceUser(appliance: Appliance, user: User, tx: EntityManager) {\n const { id, domain } = appliance\n const userRepository = getRepository(User, tx)\n\n let appuser: User = await userRepository.findOne({\n where: {\n reference: id,\n userType: 'appliance'\n },\n relations: ['domains']\n })\n\n if (!appuser) {\n const username = appuser?.username || `${appliance.id}@${domain?.subdomain}.z`\n\n appuser = await userRepository.save({\n username,\n email: username,\n name: appliance.name,\n userType: 'appliance',\n reference: id,\n status: UserStatus.ACTIVATED,\n domains: [domain],\n updater: user,\n creator: appuser?.creator || user\n })\n\n return await userRepository.findOne({\n where: {\n id: appuser.id\n },\n relations: ['domains']\n })\n } else if (!appuser.username) {\n /* username을 사용하지 않았던 하위버전에서 생성된 데이타를 자동으로 수정하기 위한 로직임. */\n const username = appuser.email || `${appliance.id}@${domain?.subdomain}.z`\n\n appuser = await userRepository.save({\n ...appuser,\n username: appuser.username || username,\n updater: user\n })\n\n return await userRepository.findOne({\n where: {\n id: appuser.id\n },\n relations: ['domains']\n })\n }\n\n return appuser\n }\n}\n"]}
1
+ {"version":3,"file":"appliance.js","sourceRoot":"","sources":["../../../server/service/appliance/appliance.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n EntityManager,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Directive, Field, ID, ObjectType } from 'type-graphql'\nimport { Domain, getRepository } from '@things-factory/shell'\nimport { config } from '@things-factory/env'\n\nimport { credentialAtRestTransformer } from '../../utils/credential-at-rest.js'\n\nimport { User, UserStatus } from '../user/user.js'\nimport { SECRET } from '../../utils/get-secret.js'\nimport jwt from 'jsonwebtoken'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_appliance_0', (appliance: Appliance) => [appliance.domain, appliance.name], {\n unique: true\n})\n@ObjectType({ description: 'Represents a physical or virtual appliance that can be managed by the system.' })\nexport class Appliance {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the appliance.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain to which the appliance belongs.' })\n domain?: Domain\n\n @RelationId((appliance: Appliance) => appliance.domain)\n domainId: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'The serial number of the appliance.' })\n serialNo: string\n\n @Column()\n @Field({ description: 'The name of the appliance.' })\n name: string\n\n @Column()\n @Field({ nullable: true, description: 'The brand or manufacturer of the appliance.' })\n brand: string\n\n @Column()\n @Field({ nullable: true, description: 'The model of the appliance.' })\n model: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'The network mask of the appliance.' })\n netmask: string\n\n @Column({\n nullable: true\n })\n @Field({ nullable: true, description: 'A description of the appliance.' })\n description: string\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n transformer: credentialAtRestTransformer\n })\n @Field({ nullable: true, description: 'The access token for the appliance.' })\n @Directive('@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)')\n accessToken: string\n\n /*\n * Has this appliance's credential been taken away? Not a column.\n *\n * Revocation lives in the shadow user's status, not here — `revokeApplianceCredential` clears\n * `accessToken` as tidiness and sets that user INACTIVE, which is what actually stops the\n * token. So an empty `accessToken` says two different things: never issued, or revoked. A\n * screen that cannot tell them apart shows the same blank box for a new appliance and for a\n * lost one.\n *\n * Resolved in `ApplianceQuery`, so it costs a lookup and only in the queries that ask for it.\n */\n @Field(type => Boolean, {\n nullable: true,\n description: \"Whether this appliance's credential has been revoked.\"\n })\n credentialRevoked?: boolean\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who created the appliance.' })\n creator: User\n\n @RelationId((appliance: Appliance) => appliance.creator)\n creatorId: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who last updated the appliance.' })\n updater: User\n\n @RelationId((appliance: Appliance) => appliance.updater)\n updaterId: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the appliance was created.' })\n createdAt: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the appliance was last updated.' })\n updatedAt: Date\n\n /*\n * `cs` is the credential serial (ADR-0085 decision 3) — which generation of this appliance's\n * credential the token belongs to. It is absent for a subject that has never been through the\n * new path, and `checkAuth` lets an absent one by, which is what keeps the tokens already in\n * the field working.\n *\n * It also makes each issue distinct. The rest of this payload is the same every time and\n * `iat` counts whole seconds, so two signings inside one second used to produce the identical\n * string — revoking and immediately re-issuing handed back the credential just revoked.\n */\n static sign(subject, expiresIn, domain, user, appliance) {\n var credential: any = {\n id: user.id,\n userType: 'appliance',\n appliance: {\n id: appliance.id\n },\n status: UserStatus.ACTIVATED,\n domain: {\n subdomain: domain.subdomain\n }\n }\n\n if (user.credentialSerial !== null && user.credentialSerial !== undefined) {\n credential.cs = user.credentialSerial\n }\n\n return jwt.sign(credential, SECRET, {\n expiresIn,\n issuer: 'hatiolab.com',\n subject\n })\n }\n\n static generateAccessToken(domain, user, appliance) {\n /* how to set expiresIn https://github.com/vercel/ms */\n let expiresIn = config.get('applianceJwtExpiresIn', '1y')\n return this.sign('access-token', expiresIn, domain, user, appliance)\n }\n\n static async ensureApplianceUser(appliance: Appliance, user: User, tx: EntityManager) {\n const { id, domain } = appliance\n const userRepository = getRepository(User, tx)\n\n let appuser: User = await userRepository.findOne({\n where: {\n reference: id,\n userType: 'appliance'\n },\n relations: ['domains']\n })\n\n if (!appuser) {\n const username = appuser?.username || `${appliance.id}@${domain?.subdomain}.z`\n\n appuser = await userRepository.save({\n username,\n email: username,\n name: appliance.name,\n userType: 'appliance',\n reference: id,\n status: UserStatus.ACTIVATED,\n domains: [domain],\n updater: user,\n creator: appuser?.creator || user\n })\n\n return await userRepository.findOne({\n where: {\n id: appuser.id\n },\n relations: ['domains']\n })\n } else if (!appuser.username) {\n /* username을 사용하지 않았던 하위버전에서 생성된 데이타를 자동으로 수정하기 위한 로직임. */\n const username = appuser.email || `${appliance.id}@${domain?.subdomain}.z`\n\n appuser = await userRepository.save({\n ...appuser,\n username: appuser.username || username,\n updater: user\n })\n\n return await userRepository.findOne({\n where: {\n id: appuser.id\n },\n relations: ['domains']\n })\n }\n\n return appuser\n }\n}\n"]}
@@ -19,12 +19,37 @@ let ApplicationMutation = class ApplicationMutation {
19
19
  updater: context.state.user
20
20
  });
21
21
  }
22
+ /*
23
+ * Deleting an application takes its bindings with it.
24
+ *
25
+ * ── Why the row alone was not enough ────────────────────────────────────
26
+ * A binding is a shadow `User` standing in for the client, and that user is what a token
27
+ * names. `User.checkAuth` looks the subject up by the id baked into the token and never asks
28
+ * whether the application still exists, so a surviving binding keeps authenticating — and
29
+ * `/oauth2/refresh-token` finds it by the refresh token in `password` and mints another year.
30
+ *
31
+ * So the screen said the application was gone while its clients kept working, with nothing
32
+ * left on any screen to reach them: the list they appear in is keyed on an application that
33
+ * no longer exists. `deleteAppliance` has always deleted its shadow user; this is the same
34
+ * shape, and the asymmetry was the defect.
35
+ *
36
+ * ── Why the application is read before anything is deleted ──────────────
37
+ * The bindings are found by `reference`, which carries no tenant of its own. Deleting them
38
+ * first and scoping only the row afterwards would clear another tenant's bindings on the way
39
+ * to discovering the application is not ours.
40
+ */
22
41
  async deleteApplication(id, context) {
23
- const { domain } = context.state;
24
- await (0, shell_1.getRepository)(application_js_1.Application).delete({
25
- domain: { id: domain.id },
26
- id
42
+ const { domain, tx } = context.state;
43
+ const application = await (0, shell_1.getRepository)(application_js_1.Application, tx).findOneBy({ domain: { id: domain.id }, id });
44
+ if (!application) {
45
+ return true;
46
+ }
47
+ /* `userType` matters: `reference` is a plain column both kinds of shadow user write to. */
48
+ await (0, shell_1.getRepository)(user_js_1.User, tx).delete({
49
+ reference: id,
50
+ userType: 'application'
27
51
  });
52
+ await (0, shell_1.getRepository)(application_js_1.Application, tx).delete({ domain: { id: domain.id }, id });
28
53
  return true;
29
54
  }
30
55
  async generateApplicationSecret(id, context) {
@@ -51,8 +76,27 @@ let ApplicationMutation = class ApplicationMutation {
51
76
  if (!application) {
52
77
  throw new Error('application not found');
53
78
  }
54
- var accessToken = application_js_1.Application.generateAccessToken(domain, appuser, application.appSecret, scope || '');
55
- var refreshToken = application_js_1.Application.generateRefreshToken(domain, appuser, application.appSecret, scope || '');
79
+ /*
80
+ * `appKey`, not `appSecret`.
81
+ *
82
+ * These two are both 32 hex characters on the same object and they are opposite kinds of
83
+ * thing: the key is the client id and is public, the secret is what `/oauth2/access-token`
84
+ * compares against to decide a caller is that client. This line passed the secret from the
85
+ * day the file was written, and it cost twice over.
86
+ *
87
+ * the secret went out a JWT payload is base64, not encryption. Whoever held one of
88
+ * these tokens could read the client secret out of it and then
89
+ * authenticate as the application.
90
+ * refreshing was broken the OAuth2 refresh exchange reads this field back and does
91
+ * `findOneBy({ appKey })` (`router/oauth2/oauth2-server.ts`). A
92
+ * payload naming the secret matched no row, so the exchange
93
+ * answered "application is not exist".
94
+ *
95
+ * Tokens minted before this fix still carry the live secret and are left alone
96
+ * (2026-09-20 사용자 결정). Nothing new goes out that way.
97
+ */
98
+ var accessToken = application_js_1.Application.generateAccessToken(domain, appuser, application, scope || '');
99
+ var refreshToken = application_js_1.Application.generateRefreshToken(domain, appuser, application, scope || '');
56
100
  await (0, shell_1.getRepository)(user_js_1.User).save({
57
101
  ...appuser,
58
102
  password: refreshToken
@@ -84,6 +128,7 @@ tslib_1.__decorate([
84
128
  ], ApplicationMutation.prototype, "createApplication", null);
85
129
  tslib_1.__decorate([
86
130
  (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
131
+ (0, type_graphql_1.Directive)('@transaction'),
87
132
  (0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To delete application' }),
88
133
  tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
89
134
  tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
@@ -1 +1 @@
1
- {"version":3,"file":"application-mutation.js","sourceRoot":"","sources":["../../../server/service/application/application-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, Arg, Ctx, Mutation, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { User } from '../user/user.js'\nimport { Application } from './application.js'\nimport { AccessToken, ApplicationPatch, NewApplication } from './application-types.js'\n\n@Resolver(Application)\nexport class ApplicationMutation {\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application, { description: 'To create new application' })\n async createApplication(@Arg('application') application: NewApplication, @Ctx() context: ResolverContext) {\n const { domain } = context.state\n return await getRepository(Application).save({\n ...application,\n domain,\n appKey: Application.generateAppKey(),\n appSecret: Application.generateAppSecret(),\n creator: context.state.user,\n updater: context.state.user\n })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'To delete application' })\n async deleteApplication(@Arg('id') id: string, @Ctx() context: ResolverContext) {\n const { domain } = context.state\n await getRepository(Application).delete({\n domain: { id: domain.id },\n id\n })\n return true\n }\n\n @Directive('@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application)\n async generateApplicationSecret(@Arg('id') id: string, @Ctx() context: ResolverContext) {\n const { domain } = context.state\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ domain: { id: domain.id }, id })\n\n return await repository.save({\n ...application,\n appSecret: Application.generateAppSecret(),\n updater: context.state.user\n })\n }\n\n @Directive('@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => AccessToken)\n async renewApplicationAccessToken(\n @Arg('id') id: string,\n @Ctx() context: ResolverContext,\n @Arg('scope') scope?: string\n ) {\n const { domain } = context.state\n\n var appuser: User = await getRepository(User).findOneBy({\n id,\n userType: 'application'\n })\n\n if (!appuser) {\n throw new Error('application is not bound')\n }\n\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ id: appuser.reference })\n if (!application) {\n throw new Error('application not found')\n }\n\n var accessToken = Application.generateAccessToken(domain, appuser, application.appSecret, scope || '')\n var refreshToken = Application.generateRefreshToken(domain, appuser, application.appSecret, scope || '')\n\n await getRepository(User).save({\n ...(appuser as any),\n password: refreshToken\n })\n\n return {\n accessToken,\n refreshToken\n }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application)\n async updateApplication(\n @Arg('id') id: string,\n @Arg('patch') patch: ApplicationPatch,\n @Ctx() context: ResolverContext\n ) {\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ id })\n\n return await repository.save({\n ...application,\n ...patch,\n updater: context.state.user\n })\n }\n}\n"]}
1
+ {"version":3,"file":"application-mutation.js","sourceRoot":"","sources":["../../../server/service/application/application-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, Arg, Ctx, Mutation, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { User } from '../user/user.js'\nimport { Application } from './application.js'\nimport { AccessToken, ApplicationPatch, NewApplication } from './application-types.js'\n\n@Resolver(Application)\nexport class ApplicationMutation {\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application, { description: 'To create new application' })\n async createApplication(@Arg('application') application: NewApplication, @Ctx() context: ResolverContext) {\n const { domain } = context.state\n return await getRepository(Application).save({\n ...application,\n domain,\n appKey: Application.generateAppKey(),\n appSecret: Application.generateAppSecret(),\n creator: context.state.user,\n updater: context.state.user\n })\n }\n\n /*\n * Deleting an application takes its bindings with it.\n *\n * ── Why the row alone was not enough ────────────────────────────────────\n * A binding is a shadow `User` standing in for the client, and that user is what a token\n * names. `User.checkAuth` looks the subject up by the id baked into the token and never asks\n * whether the application still exists, so a surviving binding keeps authenticating — and\n * `/oauth2/refresh-token` finds it by the refresh token in `password` and mints another year.\n *\n * So the screen said the application was gone while its clients kept working, with nothing\n * left on any screen to reach them: the list they appear in is keyed on an application that\n * no longer exists. `deleteAppliance` has always deleted its shadow user; this is the same\n * shape, and the asymmetry was the defect.\n *\n * ── Why the application is read before anything is deleted ──────────────\n * The bindings are found by `reference`, which carries no tenant of its own. Deleting them\n * first and scoping only the row afterwards would clear another tenant's bindings on the way\n * to discovering the application is not ours.\n */\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete application' })\n async deleteApplication(@Arg('id') id: string, @Ctx() context: ResolverContext) {\n const { domain, tx } = context.state\n\n const application = await getRepository(Application, tx).findOneBy({ domain: { id: domain.id }, id })\n\n if (!application) {\n return true\n }\n\n /* `userType` matters: `reference` is a plain column both kinds of shadow user write to. */\n await getRepository(User, tx).delete({\n reference: id,\n userType: 'application'\n })\n\n await getRepository(Application, tx).delete({ domain: { id: domain.id }, id })\n\n return true\n }\n\n @Directive('@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application)\n async generateApplicationSecret(@Arg('id') id: string, @Ctx() context: ResolverContext) {\n const { domain } = context.state\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ domain: { id: domain.id }, id })\n\n return await repository.save({\n ...application,\n appSecret: Application.generateAppSecret(),\n updater: context.state.user\n })\n }\n\n @Directive('@privilege(category: \"security\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => AccessToken)\n async renewApplicationAccessToken(\n @Arg('id') id: string,\n @Ctx() context: ResolverContext,\n @Arg('scope') scope?: string\n ) {\n const { domain } = context.state\n\n var appuser: User = await getRepository(User).findOneBy({\n id,\n userType: 'application'\n })\n\n if (!appuser) {\n throw new Error('application is not bound')\n }\n\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ id: appuser.reference })\n if (!application) {\n throw new Error('application not found')\n }\n\n /*\n * `appKey`, not `appSecret`.\n *\n * These two are both 32 hex characters on the same object and they are opposite kinds of\n * thing: the key is the client id and is public, the secret is what `/oauth2/access-token`\n * compares against to decide a caller is that client. This line passed the secret from the\n * day the file was written, and it cost twice over.\n *\n * the secret went out a JWT payload is base64, not encryption. Whoever held one of\n * these tokens could read the client secret out of it and then\n * authenticate as the application.\n * refreshing was broken the OAuth2 refresh exchange reads this field back and does\n * `findOneBy({ appKey })` (`router/oauth2/oauth2-server.ts`). A\n * payload naming the secret matched no row, so the exchange\n * answered \"application is not exist\".\n *\n * Tokens minted before this fix still carry the live secret and are left alone\n * (2026-09-20 사용자 결정). Nothing new goes out that way.\n */\n var accessToken = Application.generateAccessToken(domain, appuser, application, scope || '')\n var refreshToken = Application.generateRefreshToken(domain, appuser, application, scope || '')\n\n await getRepository(User).save({\n ...(appuser as any),\n password: refreshToken\n })\n\n return {\n accessToken,\n refreshToken\n }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Application)\n async updateApplication(\n @Arg('id') id: string,\n @Arg('patch') patch: ApplicationPatch,\n @Ctx() context: ResolverContext\n ) {\n const repository = getRepository(Application)\n const application = await repository.findOneBy({ id })\n\n return await repository.save({\n ...application,\n ...patch,\n updater: context.state.user\n })\n }\n}\n"]}
@@ -36,9 +36,15 @@ export declare class Application {
36
36
  updaterId?: string;
37
37
  static generateAppSecret(): string;
38
38
  static generateAppKey(): string;
39
- static sign(subject: any, expiresIn: any, domain: any, user: any, appKey: any, scope: any): string;
40
- static generateAccessToken(domain: any, user: any, appKey: any, scope: any): string;
41
- static generateRefreshToken(domain: any, user: any, appKey: any, scope: any): string;
39
+ static sign(subject: any, expiresIn: any, domain: any, user: any, client: {
40
+ appKey?: string;
41
+ }, scope: any): string;
42
+ static generateAccessToken(domain: any, user: any, client: {
43
+ appKey?: string;
44
+ }, scope: any): string;
45
+ static generateRefreshToken(domain: any, user: any, client: {
46
+ appKey?: string;
47
+ }, scope: any): string;
42
48
  static generateAuthCode(email: any, appKey: any, subdomain: any, scopes: any, state: any): string;
43
49
  static verifyAuthCode(authcode: any): string | jwt.JwtPayload;
44
50
  }
@@ -11,6 +11,7 @@ const env_1 = require("@things-factory/env");
11
11
  const shell_1 = require("@things-factory/shell");
12
12
  const get_secret_js_1 = require("../../utils/get-secret.js");
13
13
  const user_js_1 = require("../user/user.js");
14
+ const credential_at_rest_js_1 = require("../../utils/credential-at-rest.js");
14
15
  const ORMCONFIG = env_1.config.get('ormconfig', {});
15
16
  const DATABASE_TYPE = ORMCONFIG.type;
16
17
  var ApplicationStatus;
@@ -43,13 +44,27 @@ let Application = class Application {
43
44
  static generateAppKey() {
44
45
  return crypto_1.default.randomBytes(16).toString('hex');
45
46
  }
46
- /* signing for jsonwebtoken */
47
- static sign(subject, expiresIn, domain, user, appKey, scope) {
47
+ /*
48
+ * Signing a token for an application.
49
+ *
50
+ * ── Why this slot takes an object and not the key ───────────────────────
51
+ * An application has two 32-character hex strings on it — `appKey`, the public client id, and
52
+ * `appSecret`, the client secret. A positional `appKey: string` parameter accepts either, and
53
+ * a call site reading `..., application.appSecret, ...` looks no different from the right one.
54
+ * `renewApplicationAccessToken` passed the secret that way from the day it was written, which
55
+ * put the client secret into a payload anyone holding the token can read.
56
+ *
57
+ * Taking `{ appKey }` does not make the wrong value unreachable, but it moves the mistake to
58
+ * where it is visible: the call site has to write the field name, and a bare string no longer
59
+ * type-checks. The caller that genuinely holds only a key (the refresh route, which reads it
60
+ * back out of a token) writes `{ appKey }` and says so.
61
+ */
62
+ static sign(subject, expiresIn, domain, user, client, scope) {
48
63
  var application = {
49
64
  id: user.id,
50
65
  userType: 'application',
51
66
  application: {
52
- appKey
67
+ appKey: client?.appKey
53
68
  },
54
69
  status: user_js_1.UserStatus.ACTIVATED,
55
70
  domain: {
@@ -63,13 +78,13 @@ let Application = class Application {
63
78
  subject
64
79
  });
65
80
  }
66
- static generateAccessToken(domain, user, appKey, scope) {
81
+ static generateAccessToken(domain, user, client, scope) {
67
82
  /* how to set expiresIn https://github.com/vercel/ms */
68
- return this.sign('access-token', '30d', domain, user, appKey, scope);
83
+ return this.sign('access-token', '30d', domain, user, client, scope);
69
84
  }
70
- static generateRefreshToken(domain, user, appKey, scope) {
85
+ static generateRefreshToken(domain, user, client, scope) {
71
86
  /* how to set expiresIn https://github.com/vercel/ms */
72
- return this.sign('refresh-token', '1y', domain, user, appKey, scope);
87
+ return this.sign('refresh-token', '1y', domain, user, client, scope);
73
88
  }
74
89
  /* auth-code signing for jsonwebtoken */
75
90
  static generateAuthCode(email, appKey, subdomain, scopes, state) {
@@ -154,7 +169,8 @@ tslib_1.__decorate([
154
169
  : DATABASE_TYPE == 'mssql'
155
170
  ? 'nvarchar'
156
171
  : 'varchar',
157
- length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
172
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,
173
+ transformer: credential_at_rest_js_1.credentialAtRestTransformer
158
174
  }),
159
175
  (0, type_graphql_1.Field)({ nullable: true, description: 'The secret key for the application (client secret).' }),
160
176
  (0, type_graphql_1.Directive)('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)'),
@@ -1 +1 @@
1
- {"version":3,"file":"application.js","sourceRoot":"","sources":["../../../server/service/application/application.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'crypto'\nimport jwt from 'jsonwebtoken'\nimport { Directive, Field, ID, ObjectType, registerEnumType } from 'type-graphql'\nimport { GraphQLEmailAddress } from 'graphql-scalars'\nimport {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\n\nimport { config } from '@things-factory/env'\nimport { Domain } from '@things-factory/shell'\n\nimport { SECRET } from '../../utils/get-secret.js'\nimport { User, UserStatus } from '../user/user.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nexport enum ApplicationStatus {\n DRAFT = 'DRAFT', // The application is in a draft state and not yet active.\n ACTIVATED = 'ACTIVATED' // The application is active and can be used.\n}\n\nregisterEnumType(ApplicationStatus, {\n name: 'ApplicationStatus',\n description: 'The activation status of an application.'\n})\n\nexport enum ApplicationType {\n SELLERCRAFT = 'SELLERCRAFT', // Represents a Sellercraft application.\n XILNEX = 'XILNEX', // Represents a Xilnex application.\n MMS = 'MMS', // Represents a MMS application.\n XERO = 'XERO', // Represents a Xero application.\n OTHERS = 'OTHERS', // Represents other types of applications.\n SFTP = 'SFTP' // Represents an SFTP application.\n}\n\nregisterEnumType(ApplicationType, {\n name: 'ApplicationType',\n description: 'The type of an application.'\n})\n@Entity()\n@Index('ix_application_0', (application: Application) => [application.appKey], { unique: true })\n@ObjectType({ description: 'Represents a third-party application that can be integrated with the system.' })\nexport class Application {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the application.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain to which the application belongs.' })\n domain?: Domain\n\n @RelationId((application: Application) => application.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The name of the application.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A description of the application.' })\n description?: string\n\n @Column()\n @Field(type => GraphQLEmailAddress, { description: 'The contact email for the application.' })\n email?: string\n\n @Column()\n @Field({ description: 'The URL of the application.' })\n url?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A URL to the icon for the application.' })\n icon?: string\n\n @Column()\n @Field({ description: 'The redirect URL for OAuth2 flows.' })\n redirectUrl?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The webhook URL for the application to receive events.' })\n webhook?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The unique key for the application (client ID).' })\n appKey?: string\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'The secret key for the application (client secret).' })\n @Directive('@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)')\n appSecret?: string\n\n @Column({ default: ApplicationStatus.DRAFT })\n @Field({ description: 'The status of the application (e.g., DRAFT, ACTIVATED).' })\n status?: ApplicationStatus\n\n @Column({\n type:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'enum'\n : DATABASE_TYPE == 'oracle'\n ? 'varchar2'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n enum:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? ApplicationType\n : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: ApplicationType.OTHERS\n })\n @Field({ description: 'The type of the application.' })\n type?: ApplicationType\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the application was created.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the application was last updated.' })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who created the application.' })\n creator?: User\n\n @RelationId((application: Application) => application.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last updated the application.' })\n updater?: User\n\n @RelationId((application: Application) => application.updater)\n updaterId?: string\n\n /* generateAppSecret */\n static generateAppSecret() {\n return crypto.randomBytes(16).toString('hex')\n }\n\n static generateAppKey() {\n return crypto.randomBytes(16).toString('hex')\n }\n\n /* signing for jsonwebtoken */\n static sign(subject, expiresIn, domain, user, appKey, scope) {\n var application = {\n id: user.id,\n userType: 'application',\n application: {\n appKey\n },\n status: UserStatus.ACTIVATED,\n domain: {\n subdomain: domain.subdomain\n },\n scope\n }\n\n return jwt.sign(application, SECRET, {\n expiresIn,\n issuer: 'hatiolab.com',\n subject\n })\n }\n\n static generateAccessToken(domain, user, appKey, scope) {\n /* how to set expiresIn https://github.com/vercel/ms */\n return this.sign('access-token', '30d', domain, user, appKey, scope)\n }\n\n static generateRefreshToken(domain, user, appKey, scope) {\n /* how to set expiresIn https://github.com/vercel/ms */\n return this.sign('refresh-token', '1y', domain, user, appKey, scope)\n }\n\n /* auth-code signing for jsonwebtoken */\n static generateAuthCode(email, appKey, subdomain, scopes, state) {\n var credential = {\n email,\n appKey,\n subdomain,\n scopes,\n state\n }\n\n return jwt.sign(credential, SECRET, {\n expiresIn: '1m'\n })\n }\n\n /* auth-code signing for jsonwebtoken */\n static verifyAuthCode(authcode) {\n return jwt.verify(authcode, SECRET)\n }\n}\n"]}
1
+ {"version":3,"file":"application.js","sourceRoot":"","sources":["../../../server/service/application/application.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'crypto'\nimport jwt from 'jsonwebtoken'\nimport { Directive, Field, ID, ObjectType, registerEnumType } from 'type-graphql'\nimport { GraphQLEmailAddress } from 'graphql-scalars'\nimport {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\n\nimport { config } from '@things-factory/env'\nimport { Domain } from '@things-factory/shell'\n\nimport { SECRET } from '../../utils/get-secret.js'\nimport { User, UserStatus } from '../user/user.js'\nimport { credentialAtRestTransformer } from '../../utils/credential-at-rest.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nexport enum ApplicationStatus {\n DRAFT = 'DRAFT', // The application is in a draft state and not yet active.\n ACTIVATED = 'ACTIVATED' // The application is active and can be used.\n}\n\nregisterEnumType(ApplicationStatus, {\n name: 'ApplicationStatus',\n description: 'The activation status of an application.'\n})\n\nexport enum ApplicationType {\n SELLERCRAFT = 'SELLERCRAFT', // Represents a Sellercraft application.\n XILNEX = 'XILNEX', // Represents a Xilnex application.\n MMS = 'MMS', // Represents a MMS application.\n XERO = 'XERO', // Represents a Xero application.\n OTHERS = 'OTHERS', // Represents other types of applications.\n SFTP = 'SFTP' // Represents an SFTP application.\n}\n\nregisterEnumType(ApplicationType, {\n name: 'ApplicationType',\n description: 'The type of an application.'\n})\n@Entity()\n@Index('ix_application_0', (application: Application) => [application.appKey], { unique: true })\n@ObjectType({ description: 'Represents a third-party application that can be integrated with the system.' })\nexport class Application {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the application.' })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain to which the application belongs.' })\n domain?: Domain\n\n @RelationId((application: Application) => application.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The name of the application.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A description of the application.' })\n description?: string\n\n @Column()\n @Field(type => GraphQLEmailAddress, { description: 'The contact email for the application.' })\n email?: string\n\n @Column()\n @Field({ description: 'The URL of the application.' })\n url?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'A URL to the icon for the application.' })\n icon?: string\n\n @Column()\n @Field({ description: 'The redirect URL for OAuth2 flows.' })\n redirectUrl?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The webhook URL for the application to receive events.' })\n webhook?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The unique key for the application (client ID).' })\n appKey?: string\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined,\n transformer: credentialAtRestTransformer\n })\n @Field({ nullable: true, description: 'The secret key for the application (client secret).' })\n @Directive('@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)')\n appSecret?: string\n\n @Column({ default: ApplicationStatus.DRAFT })\n @Field({ description: 'The status of the application (e.g., DRAFT, ACTIVATED).' })\n status?: ApplicationStatus\n\n @Column({\n type:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'enum'\n : DATABASE_TYPE == 'oracle'\n ? 'varchar2'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n enum:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? ApplicationType\n : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: ApplicationType.OTHERS\n })\n @Field({ description: 'The type of the application.' })\n type?: ApplicationType\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the application was created.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'The timestamp when the application was last updated.' })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who created the application.' })\n creator?: User\n\n @RelationId((application: Application) => application.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last updated the application.' })\n updater?: User\n\n @RelationId((application: Application) => application.updater)\n updaterId?: string\n\n /* generateAppSecret */\n static generateAppSecret() {\n return crypto.randomBytes(16).toString('hex')\n }\n\n static generateAppKey() {\n return crypto.randomBytes(16).toString('hex')\n }\n\n /*\n * Signing a token for an application.\n *\n * ── Why this slot takes an object and not the key ───────────────────────\n * An application has two 32-character hex strings on it — `appKey`, the public client id, and\n * `appSecret`, the client secret. A positional `appKey: string` parameter accepts either, and\n * a call site reading `..., application.appSecret, ...` looks no different from the right one.\n * `renewApplicationAccessToken` passed the secret that way from the day it was written, which\n * put the client secret into a payload anyone holding the token can read.\n *\n * Taking `{ appKey }` does not make the wrong value unreachable, but it moves the mistake to\n * where it is visible: the call site has to write the field name, and a bare string no longer\n * type-checks. The caller that genuinely holds only a key (the refresh route, which reads it\n * back out of a token) writes `{ appKey }` and says so.\n */\n static sign(subject, expiresIn, domain, user, client: { appKey?: string }, scope) {\n var application = {\n id: user.id,\n userType: 'application',\n application: {\n appKey: client?.appKey\n },\n status: UserStatus.ACTIVATED,\n domain: {\n subdomain: domain.subdomain\n },\n scope\n }\n\n return jwt.sign(application, SECRET, {\n expiresIn,\n issuer: 'hatiolab.com',\n subject\n })\n }\n\n static generateAccessToken(domain, user, client: { appKey?: string }, scope) {\n /* how to set expiresIn https://github.com/vercel/ms */\n return this.sign('access-token', '30d', domain, user, client, scope)\n }\n\n static generateRefreshToken(domain, user, client: { appKey?: string }, scope) {\n /* how to set expiresIn https://github.com/vercel/ms */\n return this.sign('refresh-token', '1y', domain, user, client, scope)\n }\n\n /* auth-code signing for jsonwebtoken */\n static generateAuthCode(email, appKey, subdomain, scopes, state) {\n var credential = {\n email,\n appKey,\n subdomain,\n scopes,\n state\n }\n\n return jwt.sign(credential, SECRET, {\n expiresIn: '1m'\n })\n }\n\n /* auth-code signing for jsonwebtoken */\n static verifyAuthCode(authcode) {\n return jwt.verify(authcode, SECRET)\n }\n}\n"]}
@@ -108,6 +108,7 @@ let AuthProviderMutation = class AuthProviderMutation {
108
108
  };
109
109
  exports.AuthProviderMutation = AuthProviderMutation;
110
110
  tslib_1.__decorate([
111
+ (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
111
112
  (0, type_graphql_1.Directive)('@transaction'),
112
113
  (0, type_graphql_1.Mutation)(returns => auth_provider_js_1.AuthProvider, { description: 'To create new AuthProvider' }),
113
114
  tslib_1.__param(0, (0, type_graphql_1.Arg)('authProvider')),
@@ -117,6 +118,7 @@ tslib_1.__decorate([
117
118
  tslib_1.__metadata("design:returntype", Promise)
118
119
  ], AuthProviderMutation.prototype, "createAuthProvider", null);
119
120
  tslib_1.__decorate([
121
+ (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
120
122
  (0, type_graphql_1.Directive)('@transaction'),
121
123
  (0, type_graphql_1.Mutation)(returns => auth_provider_js_1.AuthProvider, { description: 'To modify AuthProvider information' }),
122
124
  tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
@@ -127,6 +129,7 @@ tslib_1.__decorate([
127
129
  tslib_1.__metadata("design:returntype", Promise)
128
130
  ], AuthProviderMutation.prototype, "updateAuthProvider", null);
129
131
  tslib_1.__decorate([
132
+ (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
130
133
  (0, type_graphql_1.Directive)('@transaction'),
131
134
  (0, type_graphql_1.Mutation)(returns => [auth_provider_js_1.AuthProvider]),
132
135
  tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [auth_provider_type_js_1.AuthProviderPatch])),
@@ -136,6 +139,7 @@ tslib_1.__decorate([
136
139
  tslib_1.__metadata("design:returntype", Promise)
137
140
  ], AuthProviderMutation.prototype, "updateMultipleAuthProvider", null);
138
141
  tslib_1.__decorate([
142
+ (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
139
143
  (0, type_graphql_1.Directive)('@transaction'),
140
144
  (0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To delete AuthProvider' }),
141
145
  tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
@@ -145,6 +149,7 @@ tslib_1.__decorate([
145
149
  tslib_1.__metadata("design:returntype", Promise)
146
150
  ], AuthProviderMutation.prototype, "deleteAuthProvider", null);
147
151
  tslib_1.__decorate([
152
+ (0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)'),
148
153
  (0, type_graphql_1.Directive)('@transaction'),
149
154
  (0, type_graphql_1.Mutation)(returns => Boolean),
150
155
  tslib_1.__param(0, (0, type_graphql_1.Arg)('ids', type => [String])),