@things-factory/auth-ui 10.1.17 → 10.1.18

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 (62) hide show
  1. package/dist-client/components/app-binding-detail.d.ts +46 -0
  2. package/dist-client/components/app-binding-detail.js +384 -0
  3. package/dist-client/components/app-binding-detail.js.map +1 -0
  4. package/dist-client/components/appliance-editor.d.ts +37 -0
  5. package/dist-client/components/appliance-editor.js +489 -0
  6. package/dist-client/components/appliance-editor.js.map +1 -0
  7. package/dist-client/components/application-editor.d.ts +31 -0
  8. package/dist-client/components/application-editor.js +491 -0
  9. package/dist-client/components/application-editor.js.map +1 -0
  10. package/dist-client/components/profile-component.js +28 -11
  11. package/dist-client/components/profile-component.js.map +1 -1
  12. package/dist-client/constants/application-type-parity.test.js +62 -0
  13. package/dist-client/constants/application-type-parity.test.js.map +1 -0
  14. package/dist-client/constants/application.d.ts +17 -7
  15. package/dist-client/constants/application.js +18 -8
  16. package/dist-client/constants/application.js.map +1 -1
  17. package/dist-client/pages/app-binding/app-bindings.d.ts +2 -0
  18. package/dist-client/pages/app-binding/app-bindings.js +19 -1
  19. package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
  20. package/dist-client/pages/appliance/home.d.ts +2 -0
  21. package/dist-client/pages/appliance/home.js +21 -3
  22. package/dist-client/pages/appliance/home.js.map +1 -1
  23. package/dist-client/pages/application/applications.d.ts +2 -0
  24. package/dist-client/pages/application/applications.js +21 -3
  25. package/dist-client/pages/application/applications.js.map +1 -1
  26. package/dist-client/pages/secrets-are-not-printed.test.js +21 -3
  27. package/dist-client/pages/secrets-are-not-printed.test.js.map +1 -1
  28. package/dist-client/route.js +0 -15
  29. package/dist-client/route.js.map +1 -1
  30. package/dist-client/tsconfig.tsbuildinfo +1 -1
  31. package/dist-client/utils/form-values.d.ts +29 -0
  32. package/dist-client/utils/form-values.js +45 -0
  33. package/dist-client/utils/form-values.js.map +1 -1
  34. package/dist-client/utils/form-values.test.js +59 -1
  35. package/dist-client/utils/form-values.test.js.map +1 -1
  36. package/dist-client/utils/webauthn-verification.d.ts +56 -0
  37. package/dist-client/utils/webauthn-verification.js +79 -0
  38. package/dist-client/utils/webauthn-verification.js.map +1 -0
  39. package/dist-client/utils/webauthn-verification.test.js +79 -0
  40. package/dist-client/utils/webauthn-verification.test.js.map +1 -0
  41. package/package.json +3 -3
  42. package/things-factory.config.js +0 -5
  43. package/translations/en.json +25 -0
  44. package/translations/ja.json +26 -0
  45. package/translations/ko.json +25 -0
  46. package/translations/ms.json +26 -0
  47. package/translations/zh.json +26 -0
  48. package/dist-client/pages/app-binding/app-binding.d.ts +0 -2
  49. package/dist-client/pages/app-binding/app-binding.js +0 -371
  50. package/dist-client/pages/app-binding/app-binding.js.map +0 -1
  51. package/dist-client/pages/appliance/appliance.d.ts +0 -2
  52. package/dist-client/pages/appliance/appliance.js +0 -390
  53. package/dist-client/pages/appliance/appliance.js.map +0 -1
  54. package/dist-client/pages/appliance/register.js +0 -166
  55. package/dist-client/pages/appliance/register.js.map +0 -1
  56. package/dist-client/pages/application/application.d.ts +0 -2
  57. package/dist-client/pages/application/application.js +0 -384
  58. package/dist-client/pages/application/application.js.map +0 -1
  59. package/dist-client/pages/application/register.js +0 -246
  60. package/dist-client/pages/application/register.js.map +0 -1
  61. /package/dist-client/{pages/appliance/register.d.ts → constants/application-type-parity.test.d.ts} +0 -0
  62. /package/dist-client/{pages/application/register.d.ts → utils/webauthn-verification.test.d.ts} +0 -0
@@ -0,0 +1,46 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@material/web/button/filled-button.js';
4
+ import { LitElement } from 'lit';
5
+ import './secret-field.js';
6
+ declare const AppBindingDetail_base: (new (...args: any[]) => LitElement) & typeof LitElement;
7
+ /**
8
+ * One application binding, in a popup.
9
+ *
10
+ * ── Read-only, and that is the point ────────────────────────────────────────
11
+ * A binding is not authored here. It is what an application ends up with after it walks the
12
+ * OAuth flow, so there is no create and no edit — only two things a person can do to one:
13
+ * renew its access token, or revoke it by deleting the binding. Everything else on this
14
+ * screen is the record saying what it is.
15
+ *
16
+ * ── Two defects the page this replaced carried ──────────────────────────────
17
+ * 1. **The expiry line said 1970.** A JWT `exp` is in seconds; the page passed it straight to
18
+ * `new Date(...)`, which reads milliseconds. So `new Date(1760000000)` — three weeks after
19
+ * the epoch — was printed as the moment a token expires, beside a countdown computed in
20
+ * seconds that was right. Two numbers for the same instant, disagreeing by half a century.
21
+ * 2. **A field was labelled `name` and showed the email.** `<input .value=${binding.email}>`
22
+ * under `<label>name</label>`.
23
+ *
24
+ * ── Why the access token starts empty ───────────────────────────────────────
25
+ * `AppBinding` stores a refresh token; an access token is minted, handed over, and not kept.
26
+ * So the access-token field has nothing in it until this screen mints a fresh one, and that
27
+ * is the honest state rather than a gap to fill.
28
+ */
29
+ export declare class AppBindingDetail extends AppBindingDetail_base {
30
+ static styles: import("lit").CSSResult[];
31
+ appBindingId?: string;
32
+ private binding;
33
+ /** Minted by `renew`, never stored on the record. Empty until someone presses it. */
34
+ private accessToken?;
35
+ private failed;
36
+ private loadFailed;
37
+ render(): import("lit-html").TemplateResult<1>;
38
+ private expiry;
39
+ connectedCallback(): Promise<void>;
40
+ fetchBinding(): Promise<void>;
41
+ private renewAccessToken;
42
+ private deleteBinding;
43
+ private close;
44
+ private expTimer;
45
+ }
46
+ export {};
@@ -0,0 +1,384 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ import '@material/web/button/text-button.js';
4
+ import '@material/web/button/filled-button.js';
5
+ import gql from 'graphql-tag';
6
+ import { css, html, LitElement } from 'lit';
7
+ import { customElement, property, state } from 'lit/decorators.js';
8
+ import { asyncReplace } from 'lit/directives/async-replace.js';
9
+ import { client } from '@operato/graphql';
10
+ import { i18next, localize } from '@operato/i18n';
11
+ import { OxPrompt } from '@operato/popup/ox-prompt.js';
12
+ import { CommonHeaderStyles, CommonPopupStyles } from '@operato/styles';
13
+ import { parseJwt, sleep } from '@operato/utils';
14
+ import './secret-field.js';
15
+ /**
16
+ * One application binding, in a popup.
17
+ *
18
+ * ── Read-only, and that is the point ────────────────────────────────────────
19
+ * A binding is not authored here. It is what an application ends up with after it walks the
20
+ * OAuth flow, so there is no create and no edit — only two things a person can do to one:
21
+ * renew its access token, or revoke it by deleting the binding. Everything else on this
22
+ * screen is the record saying what it is.
23
+ *
24
+ * ── Two defects the page this replaced carried ──────────────────────────────
25
+ * 1. **The expiry line said 1970.** A JWT `exp` is in seconds; the page passed it straight to
26
+ * `new Date(...)`, which reads milliseconds. So `new Date(1760000000)` — three weeks after
27
+ * the epoch — was printed as the moment a token expires, beside a countdown computed in
28
+ * seconds that was right. Two numbers for the same instant, disagreeing by half a century.
29
+ * 2. **A field was labelled `name` and showed the email.** `<input .value=${binding.email}>`
30
+ * under `<label>name</label>`.
31
+ *
32
+ * ── Why the access token starts empty ───────────────────────────────────────
33
+ * `AppBinding` stores a refresh token; an access token is minted, handed over, and not kept.
34
+ * So the access-token field has nothing in it until this screen mints a fresh one, and that
35
+ * is the honest state rather than a gap to fill.
36
+ */
37
+ let AppBindingDetail = class AppBindingDetail extends localize(i18next)(LitElement) {
38
+ constructor() {
39
+ super(...arguments);
40
+ this.binding = null;
41
+ this.failed = false;
42
+ this.loadFailed = false;
43
+ }
44
+ static { this.styles = [
45
+ CommonHeaderStyles,
46
+ CommonPopupStyles,
47
+ css `
48
+ :host {
49
+ display: flex;
50
+ flex-direction: column;
51
+
52
+ background-color: var(--md-sys-color-surface);
53
+ overflow: auto;
54
+ }
55
+
56
+ [body] {
57
+ flex: 1;
58
+ padding: var(--popup-padding);
59
+ }
60
+
61
+ fieldset {
62
+ border-radius: var(--border-radius);
63
+ border: var(--border-dim-color);
64
+ margin: 0 0 var(--popup-gap) 0;
65
+ padding: var(--popup-padding);
66
+ }
67
+
68
+ legend {
69
+ padding: var(--legend-padding);
70
+ font-weight: bold;
71
+ color: var(--legend-color);
72
+ }
73
+
74
+ [field-2column] {
75
+ display: grid;
76
+ grid-template-columns: 1fr 1fr;
77
+ grid-gap: var(--popup-gap);
78
+ }
79
+
80
+ [field] {
81
+ display: flex;
82
+ flex-direction: column;
83
+ }
84
+
85
+ [grid-span] {
86
+ grid-column: span 2;
87
+ }
88
+
89
+ label {
90
+ font: var(--label-font);
91
+ color: var(--label-color, var(--md-sys-color-on-surface));
92
+ text-transform: var(--label-text-transform);
93
+ }
94
+
95
+ input {
96
+ background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));
97
+ color: var(--input-field-color, var(--md-sys-color-on-surface));
98
+ border: var(--border-dim-color);
99
+ border-radius: var(--border-radius);
100
+ margin: var(--input-margin);
101
+ padding: var(--input-padding);
102
+ font: var(--input-font);
103
+
104
+ opacity: 0.7;
105
+ }
106
+
107
+ input:focus {
108
+ outline: none;
109
+ }
110
+
111
+ [input-hint] {
112
+ font-size: var(--popup-font-size-small);
113
+ color: var(--input-hint-color);
114
+ }
115
+
116
+ .buttons .left {
117
+ margin-right: auto;
118
+ }
119
+
120
+ @media screen and (max-width: 480px) {
121
+ [field] {
122
+ grid-column: span 2;
123
+ }
124
+ }
125
+ `
126
+ ]; }
127
+ render() {
128
+ if (this.loadFailed) {
129
+ return html `<div class="failure">${i18next.t('text.the binding could not be loaded')}</div>`;
130
+ }
131
+ const binding = this.binding || {};
132
+ const app = binding.application || {};
133
+ return html `
134
+ <div body>
135
+ <fieldset>
136
+ <legend>${i18next.t('text.application')}</legend>
137
+ <div field-2column>
138
+ <div field grid-span>
139
+ <label for="app-name">${i18next.t('field.name')}</label>
140
+ <input id="app-name" type="text" .value=${app.name || ''} readonly />
141
+ </div>
142
+
143
+ <div field grid-span>
144
+ <label for="app-description">${i18next.t('field.description')}</label>
145
+ <input id="app-description" type="text" .value=${app.description || ''} readonly />
146
+ </div>
147
+
148
+ <div field grid-span>
149
+ <label for="app-email">${i18next.t('field.contact email')}</label>
150
+ <input id="app-email" type="text" .value=${app.email || ''} readonly />
151
+ </div>
152
+ </div>
153
+ </fieldset>
154
+
155
+ <fieldset>
156
+ <legend>${i18next.t('text.binding')}</legend>
157
+ <div field-2column>
158
+ <div field grid-span>
159
+ <label for="binding-name">${i18next.t('field.name')}</label>
160
+ <input id="binding-name" type="text" .value=${binding.name || ''} readonly />
161
+ </div>
162
+
163
+ <div field grid-span>
164
+ <label for="binding-email">${i18next.t('field.email')}</label>
165
+ <input id="binding-email" type="text" .value=${binding.email || ''} readonly />
166
+ </div>
167
+
168
+ <div field>
169
+ <label for="binding-status">${i18next.t('field.status')}</label>
170
+ <input id="binding-status" type="text" .value=${binding.status || ''} readonly />
171
+ </div>
172
+
173
+ <div field>
174
+ <label for="binding-scope">${i18next.t('field.scope')}</label>
175
+ <input id="binding-scope" type="text" .value=${binding.scope || ''} readonly />
176
+ </div>
177
+ </div>
178
+ </fieldset>
179
+
180
+ <fieldset>
181
+ <legend>${i18next.t('text.binding credential')}</legend>
182
+ <div field-2column>
183
+ <div field grid-span>
184
+ <label for="access-token">${i18next.t('label.access token')}</label>
185
+ <secret-field id="access-token" .value=${this.accessToken || ''}></secret-field>
186
+ ${this.expiry(this.accessToken)}
187
+ </div>
188
+
189
+ <div field grid-span>
190
+ <label for="refresh-token">${i18next.t('label.refresh token')}</label>
191
+ <secret-field id="refresh-token" .value=${binding.refreshToken || ''}></secret-field>
192
+ ${this.expiry(binding.refreshToken)}
193
+ </div>
194
+ </div>
195
+ </fieldset>
196
+
197
+ ${this.failed ? html `<div class="failure">${i18next.t('text.could not be saved')}</div>` : ''}
198
+ </div>
199
+
200
+ <div class="buttons">
201
+ <md-text-button class="left" @click=${this.deleteBinding.bind(this)}>
202
+ ${i18next.t('button.delete')}
203
+ </md-text-button>
204
+ <md-text-button @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>
205
+ <md-filled-button @click=${this.renewAccessToken.bind(this)}>
206
+ ${i18next.t('button.renew access token')}
207
+ </md-filled-button>
208
+ </div>
209
+ `;
210
+ }
211
+ /*
212
+ * ⚠ A JWT `exp` is **seconds** since the epoch. The page this replaced handed it to
213
+ * `new Date()` unchanged, which reads milliseconds, and printed a date in 1970 next to a
214
+ * countdown that was computed correctly in seconds.
215
+ */
216
+ expiry(token) {
217
+ const exp = token ? parseJwt(token)?.exp : undefined;
218
+ if (!exp) {
219
+ return '';
220
+ }
221
+ return html `<div input-hint>
222
+ ${i18next.t('text.expires at x', { x: new Date(exp * 1000).toLocaleString() })} ·
223
+ ${asyncReplace(this.expTimer(exp))}
224
+ </div>`;
225
+ }
226
+ async connectedCallback() {
227
+ super.connectedCallback();
228
+ await this.fetchBinding();
229
+ }
230
+ async fetchBinding() {
231
+ const response = await client.query({
232
+ query: gql `
233
+ query ($id: String!) {
234
+ appBinding(id: $id) {
235
+ id
236
+ name
237
+ description
238
+ email
239
+ scope
240
+ status
241
+ application {
242
+ id
243
+ name
244
+ description
245
+ email
246
+ icon
247
+ }
248
+ refreshToken
249
+ }
250
+ }
251
+ `,
252
+ variables: { id: this.appBindingId }
253
+ });
254
+ /*
255
+ * `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets
256
+ * the record and an error pointing at that one field. That is not a failed read — the
257
+ * rest of the binding is there and worth showing. Only a refusal of the record itself,
258
+ * which arrives with no data, blanks the popup.
259
+ */
260
+ if (!response.data?.appBinding) {
261
+ this.loadFailed = true;
262
+ return;
263
+ }
264
+ this.loadFailed = false;
265
+ this.binding = response.data.appBinding;
266
+ }
267
+ async renewAccessToken(e) {
268
+ e.preventDefault();
269
+ const confirmed = await OxPrompt.open({
270
+ type: 'warning',
271
+ title: i18next.t('button.renew access token'),
272
+ text: i18next.t('text.the current access token stops working immediately'),
273
+ confirmButton: { text: i18next.t('button.confirm') },
274
+ cancelButton: { text: i18next.t('button.cancel') }
275
+ });
276
+ if (!confirmed) {
277
+ return;
278
+ }
279
+ const response = await client.mutate({
280
+ mutation: gql `
281
+ mutation ($id: String!) {
282
+ renewApplicationAccessToken(id: $id) {
283
+ accessToken
284
+ refreshToken
285
+ }
286
+ }
287
+ `,
288
+ variables: { id: this.appBindingId }
289
+ });
290
+ if (response.errors) {
291
+ this.failed = true;
292
+ return;
293
+ }
294
+ /*
295
+ * Stays open. The access token is minted here and stored nowhere — if this popup closed,
296
+ * the value would be gone for good.
297
+ */
298
+ const { accessToken, refreshToken } = response.data.renewApplicationAccessToken;
299
+ this.failed = false;
300
+ this.accessToken = accessToken;
301
+ this.binding = { ...this.binding, refreshToken };
302
+ this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }));
303
+ }
304
+ async deleteBinding(e) {
305
+ e.preventDefault();
306
+ const confirmed = await OxPrompt.open({
307
+ type: 'warning',
308
+ title: i18next.t('button.delete'),
309
+ text: i18next.t('text.are you sure you want to delete x', { x: this.binding?.application?.name || '' }),
310
+ confirmButton: { text: i18next.t('button.delete') },
311
+ cancelButton: { text: i18next.t('button.cancel') }
312
+ });
313
+ if (!confirmed) {
314
+ return;
315
+ }
316
+ const response = await client.mutate({
317
+ mutation: gql `
318
+ mutation ($id: String!) {
319
+ deleteAppBinding(id: $id)
320
+ }
321
+ `,
322
+ variables: { id: this.appBindingId }
323
+ });
324
+ if (response.errors) {
325
+ this.failed = true;
326
+ return;
327
+ }
328
+ this.failed = false;
329
+ this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }));
330
+ this.close();
331
+ }
332
+ close() {
333
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
334
+ }
335
+ async *expTimer(exp) {
336
+ const DAY = 24 * 60 * 60;
337
+ const HOUR = 60 * 60;
338
+ const MIN = 60;
339
+ while (this.isConnected) {
340
+ let remain = Math.floor(Number(exp) - Date.now() / 1000);
341
+ /*
342
+ * Past the expiry the arithmetic below produces "-5 days -3 hours", which reads as a
343
+ * duration rather than as the token being dead. Say it plainly and stop counting.
344
+ */
345
+ if (remain <= 0) {
346
+ yield i18next.t('text.expired');
347
+ return;
348
+ }
349
+ const days = Math.floor(remain / DAY);
350
+ remain -= days * DAY;
351
+ const hours = Math.floor(remain / HOUR);
352
+ remain -= hours * HOUR;
353
+ const mins = Math.floor(remain / MIN);
354
+ const secs = remain - mins * MIN;
355
+ yield i18next.t('text.remaining time', { days, hours, mins, secs });
356
+ await sleep(1000);
357
+ }
358
+ }
359
+ };
360
+ __decorate([
361
+ property({ type: String }),
362
+ __metadata("design:type", String)
363
+ ], AppBindingDetail.prototype, "appBindingId", void 0);
364
+ __decorate([
365
+ state(),
366
+ __metadata("design:type", Object)
367
+ ], AppBindingDetail.prototype, "binding", void 0);
368
+ __decorate([
369
+ state(),
370
+ __metadata("design:type", String)
371
+ ], AppBindingDetail.prototype, "accessToken", void 0);
372
+ __decorate([
373
+ state(),
374
+ __metadata("design:type", Boolean)
375
+ ], AppBindingDetail.prototype, "failed", void 0);
376
+ __decorate([
377
+ state(),
378
+ __metadata("design:type", Boolean)
379
+ ], AppBindingDetail.prototype, "loadFailed", void 0);
380
+ AppBindingDetail = __decorate([
381
+ customElement('app-binding-detail')
382
+ ], AppBindingDetail);
383
+ export { AppBindingDetail };
384
+ //# sourceMappingURL=app-binding-detail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-binding-detail.js","sourceRoot":"","sources":["../../client/components/app-binding-detail.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, CommonPopupStyles } from '@operato/styles'\nimport { parseJwt, sleep } from '@operato/utils'\n\nimport './secret-field.js'\n\n/**\n * One application binding, in a popup.\n *\n * ── Read-only, and that is the point ────────────────────────────────────────\n * A binding is not authored here. It is what an application ends up with after it walks the\n * OAuth flow, so there is no create and no edit — only two things a person can do to one:\n * renew its access token, or revoke it by deleting the binding. Everything else on this\n * screen is the record saying what it is.\n *\n * ── Two defects the page this replaced carried ──────────────────────────────\n * 1. **The expiry line said 1970.** A JWT `exp` is in seconds; the page passed it straight to\n * `new Date(...)`, which reads milliseconds. So `new Date(1760000000)` — three weeks after\n * the epoch — was printed as the moment a token expires, beside a countdown computed in\n * seconds that was right. Two numbers for the same instant, disagreeing by half a century.\n * 2. **A field was labelled `name` and showed the email.** `<input .value=${binding.email}>`\n * under `<label>name</label>`.\n *\n * ── Why the access token starts empty ───────────────────────────────────────\n * `AppBinding` stores a refresh token; an access token is minted, handed over, and not kept.\n * So the access-token field has nothing in it until this screen mints a fresh one, and that\n * is the honest state rather than a gap to fill.\n */\n@customElement('app-binding-detail')\nexport class AppBindingDetail extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-surface);\n overflow: auto;\n }\n\n [body] {\n flex: 1;\n padding: var(--popup-padding);\n }\n\n fieldset {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: 0 0 var(--popup-gap) 0;\n padding: var(--popup-padding);\n }\n\n legend {\n padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n [field-2column] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: var(--popup-gap);\n }\n\n [field] {\n display: flex;\n flex-direction: column;\n }\n\n [grid-span] {\n grid-column: span 2;\n }\n\n label {\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n font: var(--input-font);\n\n opacity: 0.7;\n }\n\n input:focus {\n outline: none;\n }\n\n [input-hint] {\n font-size: var(--popup-font-size-small);\n color: var(--input-hint-color);\n }\n\n .buttons .left {\n margin-right: auto;\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n @property({ type: String }) appBindingId?: string\n\n @state() private binding: any = null\n\n /** Minted by `renew`, never stored on the record. Empty until someone presses it. */\n @state() private accessToken?: string\n\n @state() private failed: boolean = false\n\n @state() private loadFailed: boolean = false\n\n render() {\n if (this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the binding could not be loaded')}</div>`\n }\n\n const binding = this.binding || {}\n const app = binding.application || {}\n\n return html`\n <div body>\n <fieldset>\n <legend>${i18next.t('text.application')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"app-name\">${i18next.t('field.name')}</label>\n <input id=\"app-name\" type=\"text\" .value=${app.name || ''} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"app-description\">${i18next.t('field.description')}</label>\n <input id=\"app-description\" type=\"text\" .value=${app.description || ''} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"app-email\">${i18next.t('field.contact email')}</label>\n <input id=\"app-email\" type=\"text\" .value=${app.email || ''} readonly />\n </div>\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"binding-name\">${i18next.t('field.name')}</label>\n <input id=\"binding-name\" type=\"text\" .value=${binding.name || ''} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"binding-email\">${i18next.t('field.email')}</label>\n <input id=\"binding-email\" type=\"text\" .value=${binding.email || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-status\">${i18next.t('field.status')}</label>\n <input id=\"binding-status\" type=\"text\" .value=${binding.status || ''} readonly />\n </div>\n\n <div field>\n <label for=\"binding-scope\">${i18next.t('field.scope')}</label>\n <input id=\"binding-scope\" type=\"text\" .value=${binding.scope || ''} readonly />\n </div>\n </div>\n </fieldset>\n\n <fieldset>\n <legend>${i18next.t('text.binding credential')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"access-token\">${i18next.t('label.access token')}</label>\n <secret-field id=\"access-token\" .value=${this.accessToken || ''}></secret-field>\n ${this.expiry(this.accessToken)}\n </div>\n\n <div field grid-span>\n <label for=\"refresh-token\">${i18next.t('label.refresh token')}</label>\n <secret-field id=\"refresh-token\" .value=${binding.refreshToken || ''}></secret-field>\n ${this.expiry(binding.refreshToken)}\n </div>\n </div>\n </fieldset>\n\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </div>\n\n <div class=\"buttons\">\n <md-text-button class=\"left\" @click=${this.deleteBinding.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n <md-text-button @click=${this.close.bind(this)}>${i18next.t('button.close')}</md-text-button>\n <md-filled-button @click=${this.renewAccessToken.bind(this)}>\n ${i18next.t('button.renew access token')}\n </md-filled-button>\n </div>\n `\n }\n\n /*\n * ⚠ A JWT `exp` is **seconds** since the epoch. The page this replaced handed it to\n * `new Date()` unchanged, which reads milliseconds, and printed a date in 1970 next to a\n * countdown that was computed correctly in seconds.\n */\n private expiry(token?: string) {\n const exp = token ? parseJwt(token)?.exp : undefined\n\n if (!exp) {\n return ''\n }\n\n return html`<div input-hint>\n ${i18next.t('text.expires at x', { x: new Date(exp * 1000).toLocaleString() })} ·\n ${asyncReplace(this.expTimer(exp))}\n </div>`\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n await this.fetchBinding()\n }\n\n async fetchBinding() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n appBinding(id: $id) {\n id\n name\n description\n email\n scope\n status\n application {\n id\n name\n description\n email\n icon\n }\n refreshToken\n }\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n /*\n * `AppBinding.refreshToken` carries its own privilege gate, so a reader without it gets\n * the record and an error pointing at that one field. That is not a failed read — the\n * rest of the binding is there and worth showing. Only a refusal of the record itself,\n * which arrives with no data, blanks the popup.\n */\n if (!response.data?.appBinding) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.binding = response.data.appBinding\n }\n\n private async renewAccessToken(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.renew access token'),\n text: i18next.t('text.the current access token stops working immediately'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n renewApplicationAccessToken(id: $id) {\n accessToken\n refreshToken\n }\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open. The access token is minted here and stored nowhere — if this popup closed,\n * the value would be gone for good.\n */\n const { accessToken, refreshToken } = response.data.renewApplicationAccessToken\n\n this.failed = false\n this.accessToken = accessToken\n this.binding = { ...this.binding, refreshToken }\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteBinding(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.are you sure you want to delete x', { x: this.binding?.application?.name || '' }),\n confirmButton: { text: i18next.t('button.delete') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteAppBinding(id: $id)\n }\n `,\n variables: { id: this.appBindingId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.failed = false\n this.dispatchEvent(new CustomEvent('app-binding-changed', { bubbles: true, composed: true }))\n this.close()\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n }\n\n private async *expTimer(exp: number) {\n const DAY = 24 * 60 * 60\n const HOUR = 60 * 60\n const MIN = 60\n\n while (this.isConnected) {\n let remain = Math.floor(Number(exp) - Date.now() / 1000)\n\n /*\n * Past the expiry the arithmetic below produces \"-5 days -3 hours\", which reads as a\n * duration rather than as the token being dead. Say it plainly and stop counting.\n */\n if (remain <= 0) {\n yield i18next.t('text.expired')\n return\n }\n\n const days = Math.floor(remain / DAY)\n remain -= days * DAY\n const hours = Math.floor(remain / HOUR)\n remain -= hours * HOUR\n const mins = Math.floor(remain / MIN)\n const secs = remain - mins * MIN\n\n yield i18next.t('text.remaining time', { days, hours, mins, secs })\n\n await sleep(1000)\n }\n }\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@material/web/button/filled-button.js';
4
+ import { LitElement } from 'lit';
5
+ import './secret-field.js';
6
+ declare const ApplianceEditor_base: (new (...args: any[]) => LitElement) & typeof LitElement;
7
+ export declare class ApplianceEditor extends ApplianceEditor_base {
8
+ static styles: import("lit").CSSResult[];
9
+ /** The appliance to edit. Absent means this popup is registering a new one. */
10
+ applianceId?: string;
11
+ form: HTMLFormElement;
12
+ /** The loaded record. The patch is a diff against this, so it is never rebuilt from the form. */
13
+ private appliance;
14
+ /** Required fields still empty. Filled in when the person presses save, not while typing. */
15
+ private missing;
16
+ /** The mutation was refused. The shared link already said why; this says the press landed. */
17
+ private failed;
18
+ /** The single-record read did not answer. Distinct from answering with nothing. */
19
+ private loadFailed;
20
+ private get editing();
21
+ render(): import("lit-html").TemplateResult<1>;
22
+ private renderCredential;
23
+ private fieldError;
24
+ connectedCallback(): Promise<void>;
25
+ fetchAppliance(): Promise<void>;
26
+ private cancel;
27
+ save(e: Event): Promise<void>;
28
+ private createAppliance;
29
+ private updateAppliance;
30
+ private generateSecret;
31
+ private deleteAppliance;
32
+ /** The list has to refetch, and the popup is done. */
33
+ private saved;
34
+ private close;
35
+ private expTimer;
36
+ }
37
+ export {};