@things-factory/auth-ui 10.1.16 → 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.
- package/dist-client/components/app-binding-detail.d.ts +46 -0
- package/dist-client/components/app-binding-detail.js +384 -0
- package/dist-client/components/app-binding-detail.js.map +1 -0
- package/dist-client/components/appliance-editor.d.ts +37 -0
- package/dist-client/components/appliance-editor.js +489 -0
- package/dist-client/components/appliance-editor.js.map +1 -0
- package/dist-client/components/application-editor.d.ts +31 -0
- package/dist-client/components/application-editor.js +491 -0
- package/dist-client/components/application-editor.js.map +1 -0
- package/dist-client/components/profile-component.js +28 -11
- package/dist-client/components/profile-component.js.map +1 -1
- package/dist-client/components/secret-field.d.ts +35 -0
- package/dist-client/components/secret-field.js +149 -0
- package/dist-client/components/secret-field.js.map +1 -0
- package/dist-client/constants/application-type-parity.test.js +62 -0
- package/dist-client/constants/application-type-parity.test.js.map +1 -0
- package/dist-client/constants/application.d.ts +17 -7
- package/dist-client/constants/application.js +18 -8
- package/dist-client/constants/application.js.map +1 -1
- package/dist-client/pages/app-binding/app-binding-list-query.test.js +46 -0
- package/dist-client/pages/app-binding/app-binding-list-query.test.js.map +1 -0
- package/dist-client/pages/app-binding/app-bindings.d.ts +65 -2
- package/dist-client/pages/app-binding/app-bindings.js +217 -117
- package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
- package/dist-client/pages/appliance/appliance-list-query.test.d.ts +1 -0
- package/dist-client/pages/appliance/appliance-list-query.test.js +58 -0
- package/dist-client/pages/appliance/appliance-list-query.test.js.map +1 -0
- package/dist-client/pages/appliance/home.d.ts +63 -2
- package/dist-client/pages/appliance/home.js +219 -123
- package/dist-client/pages/appliance/home.js.map +1 -1
- package/dist-client/pages/application/applications.d.ts +63 -2
- package/dist-client/pages/application/applications.js +212 -129
- package/dist-client/pages/application/applications.js.map +1 -1
- package/dist-client/pages/auth-provider/auth-provider-management.d.ts +4 -2
- package/dist-client/pages/auth-provider/auth-provider-management.js +35 -6
- package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
- package/dist-client/pages/domain-owner/domain-owner-management.d.ts +2 -0
- package/dist-client/pages/domain-owner/domain-owner-management.js +34 -7
- package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
- package/dist-client/pages/secrets-are-not-printed.test.d.ts +1 -0
- package/dist-client/pages/secrets-are-not-printed.test.js +88 -0
- package/dist-client/pages/secrets-are-not-printed.test.js.map +1 -0
- package/dist-client/route.js +0 -15
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/application-status.d.ts +32 -0
- package/dist-client/utils/application-status.js +45 -0
- package/dist-client/utils/application-status.js.map +1 -0
- package/dist-client/utils/application-status.test.d.ts +1 -0
- package/dist-client/utils/application-status.test.js +45 -0
- package/dist-client/utils/application-status.test.js.map +1 -0
- package/dist-client/utils/form-values.d.ts +71 -0
- package/dist-client/utils/form-values.js +102 -0
- package/dist-client/utils/form-values.js.map +1 -0
- package/dist-client/utils/form-values.test.d.ts +1 -0
- package/dist-client/utils/form-values.test.js +120 -0
- package/dist-client/utils/form-values.test.js.map +1 -0
- package/dist-client/utils/list-result.d.ts +62 -0
- package/dist-client/utils/list-result.js +92 -0
- package/dist-client/utils/list-result.js.map +1 -0
- package/dist-client/utils/list-result.test.d.ts +1 -0
- package/dist-client/utils/list-result.test.js +160 -0
- package/dist-client/utils/list-result.test.js.map +1 -0
- package/dist-client/utils/secret-display.d.ts +51 -0
- package/dist-client/utils/secret-display.js +61 -0
- package/dist-client/utils/secret-display.js.map +1 -0
- package/dist-client/utils/secret-display.test.d.ts +1 -0
- package/dist-client/utils/secret-display.test.js +59 -0
- package/dist-client/utils/secret-display.test.js.map +1 -0
- package/dist-client/utils/webauthn-verification.d.ts +56 -0
- package/dist-client/utils/webauthn-verification.js +79 -0
- package/dist-client/utils/webauthn-verification.js.map +1 -0
- package/dist-client/utils/webauthn-verification.test.d.ts +1 -0
- package/dist-client/utils/webauthn-verification.test.js +79 -0
- package/dist-client/utils/webauthn-verification.test.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/things-factory.config.js +0 -5
- package/translations/en.json +56 -0
- package/translations/ja.json +57 -0
- package/translations/ko.json +56 -0
- package/translations/ms.json +57 -0
- package/translations/zh.json +57 -0
- package/dist-client/pages/app-binding/app-binding.d.ts +0 -1
- package/dist-client/pages/app-binding/app-binding.js +0 -371
- package/dist-client/pages/app-binding/app-binding.js.map +0 -1
- package/dist-client/pages/appliance/appliance.d.ts +0 -1
- package/dist-client/pages/appliance/appliance.js +0 -392
- package/dist-client/pages/appliance/appliance.js.map +0 -1
- package/dist-client/pages/appliance/register.js +0 -166
- package/dist-client/pages/appliance/register.js.map +0 -1
- package/dist-client/pages/application/application.d.ts +0 -1
- package/dist-client/pages/application/application.js +0 -377
- package/dist-client/pages/application/application.js.map +0 -1
- package/dist-client/pages/application/register.js +0 -192
- package/dist-client/pages/application/register.js.map +0 -1
- /package/dist-client/{pages/appliance/register.d.ts → constants/application-type-parity.test.d.ts} +0 -0
- /package/dist-client/pages/{application/register.d.ts → app-binding/app-binding-list-query.test.d.ts} +0 -0
|
@@ -1,149 +1,249 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import '@material/web/
|
|
3
|
-
import '@
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@operato/data-grist/ox-grist.js';
|
|
4
|
+
import '@operato/context/ox-context-page-toolbar.js';
|
|
4
5
|
import gql from 'graphql-tag';
|
|
5
6
|
import { css, html } from 'lit';
|
|
6
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
7
|
-
import { client } from '@operato/graphql';
|
|
7
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
8
|
+
import { client, gqlContext } from '@operato/graphql';
|
|
9
|
+
import { i18next, localize } from '@operato/i18n';
|
|
10
|
+
import { openPopup } from '@operato/layout';
|
|
8
11
|
import { navigate, PageView } from '@operato/shell';
|
|
9
|
-
|
|
12
|
+
import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles';
|
|
13
|
+
import { isMobileDevice } from '@operato/utils';
|
|
14
|
+
import { p13n } from '@operato/p13n';
|
|
15
|
+
import '../../components/app-binding-detail.js';
|
|
16
|
+
import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
|
|
17
|
+
/**
|
|
18
|
+
* Applications bound to this domain.
|
|
19
|
+
*
|
|
20
|
+
* A binding is a `User` row with `userType: 'application'` — the account an application signs
|
|
21
|
+
* in as. The list shows who it is and what it may do; what it signs in *with* is elsewhere.
|
|
22
|
+
*
|
|
23
|
+
* ── The server answers honestly now ──────────────────────────────────────────
|
|
24
|
+
* Until dde62cd6a1 this query paged before it filtered: the database took a page of users of
|
|
25
|
+
* every type, JavaScript kept the applications among them, and the total reported how many
|
|
26
|
+
* survived that one page. A grid told there were three when there were thirty never offered a
|
|
27
|
+
* second page. Wiring paging up was pointless until that was fixed; it works now.
|
|
28
|
+
*
|
|
29
|
+
* ── ⚠ The refresh token is not asked for, and a test holds that ──────────────
|
|
30
|
+
* AppBinding's refresh token is the application's actual credential — oauth2-server.ts writes
|
|
31
|
+
* it into the password column verbatim, not hashed — and the field carries its own
|
|
32
|
+
* privilege directive for security:query.
|
|
33
|
+
*
|
|
34
|
+
* A credential that can be exchanged for access tokens does not belong in a grid cell, where
|
|
35
|
+
* it sits in plain text beside a name for anyone who can open the screen, and in any
|
|
36
|
+
* screenshot of it. `app-binding-list-query.test.ts` fails if the word appears here.
|
|
37
|
+
*/
|
|
38
|
+
let AppBindings = class AppBindings extends p13n(localize(i18next)(PageView)) {
|
|
10
39
|
constructor() {
|
|
11
40
|
super(...arguments);
|
|
12
|
-
this.
|
|
41
|
+
this.active = false;
|
|
42
|
+
this.mode = isMobileDevice() ? 'CARD' : 'GRID';
|
|
43
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
44
|
+
this.failed = false;
|
|
13
45
|
}
|
|
14
46
|
static { this.styles = [
|
|
47
|
+
CommonGristStyles,
|
|
48
|
+
CommonHeaderStyles,
|
|
49
|
+
ScrollbarStyles,
|
|
15
50
|
css `
|
|
16
|
-
:host {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
overflow:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
color: var(--title-text-color);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
[page-description] { margin: var(--page-description-margin);
|
|
36
|
-
font: var(--page-description-font);
|
|
37
|
-
color: var(--page-description-color);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
table { width: 100%;
|
|
41
|
-
margin: var(--spacing-large) 0;
|
|
42
|
-
border-collapse: collapse;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
tr { background-color: var(--tr-background-color);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
tr:nth-child(odd) { background-color: var(--tr-background-odd-color);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
tr:hover { background-color: var(--tr-background-hover-color);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
th { border-top: var(--th-border-top);
|
|
55
|
-
border-bottom: var(--td-border-bottom);
|
|
56
|
-
padding: var(--th-padding);
|
|
57
|
-
|
|
58
|
-
font: var(--th-font);
|
|
59
|
-
color: var(--th-color);
|
|
60
|
-
text-transform: var(--th-text-transform);
|
|
61
|
-
text-align: left;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
td { padding: var(--td-padding);
|
|
65
|
-
border-bottom: var(--td-border-bottom);
|
|
66
|
-
font: var(--td-font);
|
|
67
|
-
color: var(--td-color);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
td a { color: var(--md-sys-color-primary);
|
|
71
|
-
font: bold 16px var(--theme-font);
|
|
72
|
-
|
|
73
|
-
display: block;
|
|
74
|
-
text-decoration: none;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.text-align-center { text-align: center;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.text-align-right { text-align: right;
|
|
81
|
-
}
|
|
51
|
+
:host {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ox-grist {
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
flex: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[failed] {
|
|
63
|
+
padding: var(--spacing-medium);
|
|
64
|
+
color: var(--md-sys-color-error);
|
|
65
|
+
font: var(--label-font, inherit);
|
|
66
|
+
}
|
|
82
67
|
`
|
|
83
68
|
]; }
|
|
84
69
|
get context() {
|
|
85
|
-
return {
|
|
86
|
-
|
|
70
|
+
return {
|
|
71
|
+
title: i18next.t('text.bound applications'),
|
|
72
|
+
help: 'auth/application',
|
|
73
|
+
search: {
|
|
74
|
+
handler: (search) => {
|
|
75
|
+
this.grist.searchText = search;
|
|
76
|
+
},
|
|
77
|
+
value: this.grist?.searchText || ''
|
|
78
|
+
},
|
|
79
|
+
actions: [
|
|
80
|
+
{
|
|
81
|
+
title: i18next.t('text.applications'),
|
|
82
|
+
action: () => navigate('applications'),
|
|
83
|
+
icon: 'apps'
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
toolbar: false
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
render() {
|
|
90
|
-
var appBindings = this.appBindings || [];
|
|
91
90
|
return html `
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<tr>
|
|
105
|
-
<td>
|
|
106
|
-
<a href=${`app-binding/${appBinding.id}`}>${appBinding.name}</a>
|
|
107
|
-
${appBinding.description}
|
|
108
|
-
</td>
|
|
109
|
-
<td>${appBinding.scope}</td>
|
|
110
|
-
<td>${appBinding.status}</td>
|
|
111
|
-
</tr>
|
|
112
|
-
`)}
|
|
113
|
-
</table>
|
|
114
|
-
<md-elevated-button @click=${e => navigate('applications')}>registered applications ..</md-elevated-button>
|
|
91
|
+
<ox-grist
|
|
92
|
+
.mode=${this.mode}
|
|
93
|
+
.config=${this.config}
|
|
94
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
95
|
+
.personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
|
|
96
|
+
>
|
|
97
|
+
<div slot="headroom" class="header">
|
|
98
|
+
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
99
|
+
</div>
|
|
100
|
+
</ox-grist>
|
|
101
|
+
|
|
102
|
+
${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
|
|
115
103
|
`;
|
|
116
104
|
}
|
|
117
|
-
async
|
|
105
|
+
async pageInitialized() {
|
|
106
|
+
this.config = {
|
|
107
|
+
rows: { appendable: false, selectable: { multiple: false } },
|
|
108
|
+
columns: [
|
|
109
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
110
|
+
{
|
|
111
|
+
type: 'gutter',
|
|
112
|
+
gutterName: 'button',
|
|
113
|
+
icon: 'chevron_right',
|
|
114
|
+
title: i18next.t('button.detail'),
|
|
115
|
+
handlers: {
|
|
116
|
+
click: (_columns, _data, _column, record) => {
|
|
117
|
+
if (record?.id) {
|
|
118
|
+
this.openDetail(record.id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'string',
|
|
125
|
+
name: 'name',
|
|
126
|
+
header: i18next.t('field.name'),
|
|
127
|
+
record: { editable: false },
|
|
128
|
+
sortable: true,
|
|
129
|
+
width: 180
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'string',
|
|
133
|
+
name: 'applicationName',
|
|
134
|
+
header: i18next.t('label.application'),
|
|
135
|
+
record: { editable: false },
|
|
136
|
+
width: 180
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'string',
|
|
140
|
+
name: 'email',
|
|
141
|
+
header: i18next.t('field.email'),
|
|
142
|
+
record: { editable: false },
|
|
143
|
+
sortable: true,
|
|
144
|
+
width: 220
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'string',
|
|
148
|
+
name: 'scope',
|
|
149
|
+
header: i18next.t('field.scope'),
|
|
150
|
+
record: { editable: false },
|
|
151
|
+
width: 220
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'string',
|
|
155
|
+
name: 'status',
|
|
156
|
+
header: i18next.t('field.status'),
|
|
157
|
+
record: { align: 'center', editable: false },
|
|
158
|
+
sortable: true,
|
|
159
|
+
width: 130
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: 'datetime',
|
|
163
|
+
name: 'updatedAt',
|
|
164
|
+
header: i18next.t('field.updated at'),
|
|
165
|
+
sortable: true,
|
|
166
|
+
width: 180
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/*
|
|
172
|
+
* A binding is not authored — it is what an application ends up with after the OAuth flow —
|
|
173
|
+
* so this popup shows one and offers the two things a person can do to it. `app-binding/{id}`
|
|
174
|
+
* was a route; nothing outside auth-ui linked to it.
|
|
175
|
+
*/
|
|
176
|
+
openDetail(appBindingId) {
|
|
177
|
+
const popup = openPopup(html `<app-binding-detail
|
|
178
|
+
.appBindingId=${appBindingId}
|
|
179
|
+
@app-binding-changed=${() => this.grist.fetch()}
|
|
180
|
+
@close=${() => popup.close()}
|
|
181
|
+
></app-binding-detail>`, {
|
|
182
|
+
backdrop: true,
|
|
183
|
+
size: 'medium',
|
|
184
|
+
title: i18next.t('text.bound application')
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async pageUpdated(_changes, _lifecycle) {
|
|
118
188
|
if (this.active) {
|
|
119
|
-
|
|
189
|
+
await this.updateComplete;
|
|
190
|
+
this.grist.fetch();
|
|
120
191
|
}
|
|
121
192
|
}
|
|
122
|
-
async
|
|
123
|
-
const response = await client.query({
|
|
124
|
-
|
|
193
|
+
async fetchHandler({ page, limit, sortings = [], filters = [] }) {
|
|
194
|
+
const response = await client.query({
|
|
195
|
+
query: gql `
|
|
196
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
197
|
+
appBindings(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
198
|
+
items {
|
|
199
|
+
id
|
|
125
200
|
name
|
|
126
|
-
|
|
127
|
-
name
|
|
128
|
-
}
|
|
129
|
-
scope
|
|
201
|
+
email
|
|
130
202
|
status
|
|
131
|
-
|
|
203
|
+
scope
|
|
204
|
+
updatedAt
|
|
205
|
+
application {
|
|
206
|
+
id
|
|
207
|
+
name
|
|
208
|
+
}
|
|
209
|
+
}
|
|
132
210
|
total
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
`
|
|
136
|
-
});
|
|
137
|
-
if (!response.errors) {
|
|
138
|
-
return response.data.appBindings;
|
|
211
|
+
}
|
|
139
212
|
}
|
|
213
|
+
`,
|
|
214
|
+
variables: { filters, pagination: { page, limit }, sortings },
|
|
215
|
+
context: gqlContext()
|
|
216
|
+
});
|
|
217
|
+
const { items, total, failed } = listOutcome(pageFromResponse(response, 'appBindings'));
|
|
218
|
+
this.failed = failed;
|
|
219
|
+
return {
|
|
220
|
+
total,
|
|
221
|
+
records: items.map((item) => ({ ...item, applicationName: item.application?.name || '' }))
|
|
222
|
+
};
|
|
140
223
|
}
|
|
141
224
|
};
|
|
142
225
|
__decorate([
|
|
143
|
-
property({ type:
|
|
144
|
-
__metadata("design:type",
|
|
145
|
-
], AppBindings.prototype, "
|
|
226
|
+
property({ type: Boolean }),
|
|
227
|
+
__metadata("design:type", Boolean)
|
|
228
|
+
], AppBindings.prototype, "active", void 0);
|
|
229
|
+
__decorate([
|
|
230
|
+
property({ type: Object }),
|
|
231
|
+
__metadata("design:type", Object)
|
|
232
|
+
], AppBindings.prototype, "config", void 0);
|
|
233
|
+
__decorate([
|
|
234
|
+
property({ type: String }),
|
|
235
|
+
__metadata("design:type", String)
|
|
236
|
+
], AppBindings.prototype, "mode", void 0);
|
|
237
|
+
__decorate([
|
|
238
|
+
property({ type: Boolean }),
|
|
239
|
+
__metadata("design:type", Boolean)
|
|
240
|
+
], AppBindings.prototype, "failed", void 0);
|
|
241
|
+
__decorate([
|
|
242
|
+
query('ox-grist'),
|
|
243
|
+
__metadata("design:type", Function)
|
|
244
|
+
], AppBindings.prototype, "grist", void 0);
|
|
146
245
|
AppBindings = __decorate([
|
|
147
246
|
customElement('app-bindings-page')
|
|
148
247
|
], AppBindings);
|
|
248
|
+
export { AppBindings };
|
|
149
249
|
//# sourceMappingURL=app-bindings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-bindings.js","sourceRoot":"","sources":["../../../client/pages/app-binding/app-bindings.ts"],"names":[],"mappings":";AAAA,OAAO,yCAAyC,CAAA;AAChD,OAAO,yCAAyC,CAAA;AAEhD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGnD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,QAAQ;IAAlC;;QAuE6B,gBAAW,GAAU,EAAE,CAAA;IA4DpD,CAAC;aAnI6C,WAAM,GAAG;QACnD,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmEF;KACF,AArEiD,CAqEjD;IAID,IAAI,OAAO;QAAS,OAAO,EAAQ,KAAK,EAAE,sBAAsB;YAC5D,IAAI,EAAE,kBAAkB;SAC5B,CAAA;IACD,CAAC;IAEA,MAAM;QAAS,IAAI,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;QAErD,OAAO,IAAI,CAAA;;;;;;;;;;;;UAYL,WAAW,CAAC,GAAG,CACf,UAAU,CAAC,EAAE,CAAC,IAAI,CAAA;;;0BAGF,eAAe,UAAU,CAAC,EAAE,EAAE,IAAI,UAAU,CAAC,IAAI;kBACzD,UAAU,CAAC,WAAW;;oBAEpB,UAAU,CAAC,KAAK;oBAChB,UAAU,CAAC,MAAM;;WAE1B,CACF;;mCAE0B,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;KAC3D,CAAA;IACJ,CAAC;IAEA,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM;QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAAO,IAAI,CAAC,WAAW,GAAG,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,KAAK,CAAA;QACtI,CAAC;IACD,CAAC;IAEA,KAAK,CAAC,gBAAgB;QAAS,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAQ,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;OAYhF;SACL,CAAC,CAAA;QAEC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAAO,OAAO,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAA;QACjE,CAAC;IACD,CAAC;;AA3D2B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;gDAAwB;AAvE9C,WAAW;IADhB,aAAa,CAAC,mBAAmB,CAAC;GAC7B,WAAW,CAmIhB","sourcesContent":["import '@material/web/button/elevated-button.js'\nimport '@material/web/button/outlined-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { navigate, PageView } from '@operato/shell'\n\n@customElement('app-bindings-page')\nclass AppBindings extends PageView { static styles = [\n css`\n :host { background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\n\n overflow: auto;\n }\n\n md-elevated-button { text-transform: capitalize;\n }\n\n md-outlined-button { float: right;\n margin-top: var(--spacing-medium);\n text-transform: capitalize;\n }\n\n h2 { margin: var(--title-margin);\n font: var(--title-font);\n color: var(--title-text-color);\n }\n\n [page-description] { margin: var(--page-description-margin);\n font: var(--page-description-font);\n color: var(--page-description-color);\n }\n\n table { width: 100%;\n margin: var(--spacing-large) 0;\n border-collapse: collapse;\n }\n\n tr { background-color: var(--tr-background-color);\n }\n\n tr:nth-child(odd) { background-color: var(--tr-background-odd-color);\n }\n\n tr:hover { background-color: var(--tr-background-hover-color);\n }\n\n th { border-top: var(--th-border-top);\n border-bottom: var(--td-border-bottom);\n padding: var(--th-padding);\n\n font: var(--th-font);\n color: var(--th-color);\n text-transform: var(--th-text-transform);\n text-align: left;\n }\n\n td { padding: var(--td-padding);\n border-bottom: var(--td-border-bottom);\n font: var(--td-font);\n color: var(--td-color);\n }\n\n td a { color: var(--md-sys-color-primary);\n font: bold 16px var(--theme-font);\n\n display: block;\n text-decoration: none;\n }\n\n .text-align-center { text-align: center;\n }\n\n .text-align-right { text-align: right;\n }\n `\n ]\n\n @property({ type: Array }) appBindings: any[] = []\n\n get context() { return { title: `application bindings`,\n help: 'auth/application'\n }\n }\n\n render() { var appBindings = this.appBindings || []\n\n return html`\n <div>\n <h2>Bound Applications</h2>\n <p page-description>Bound Applications goes here</p>\n </div>\n\n <table>\n <tr>\n <th>app name</th>\n <th>Scope</th>\n <th>status</th>\n </tr>\n ${appBindings.map(\n appBinding => html`\n <tr>\n <td>\n <a href=${`app-binding/${appBinding.id}`}>${appBinding.name}</a>\n ${appBinding.description}\n </td>\n <td>${appBinding.scope}</td>\n <td>${appBinding.status}</td>\n </tr>\n `\n )}\n </table>\n <md-elevated-button @click=${e => navigate('applications')}>registered applications ..</md-elevated-button>\n `\n }\n\n async pageUpdated(changes, lifecycle, before) { if (this.active) { this.appBindings = (await this.fetchAppBindings()).items\n }\n }\n\n async fetchAppBindings() { const response = await client.query({ query: gql`\n query { appBindings { items { id\n name\n application { id\n name\n }\n scope\n status\n }\n total\n }\n }\n `\n })\n\n if (!response.errors) { return response.data.appBindings\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"app-bindings.js","sourceRoot":"","sources":["../../../client/pages/app-binding/app-bindings.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,iCAAiC,CAAA;AACxC,OAAO,6CAA6C,CAAA;AAEpD,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAGlE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAEpC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAE1E;;;;;;;;;;;;;;;;;;;;GAoBG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAA3D;;QAyBwB,WAAM,GAAY,KAAK,CAAA;QAExB,SAAI,GAA6B,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QAE/F,2EAA2E;QAC9C,WAAM,GAAY,KAAK,CAAA;IA0KtD,CAAC;aAvMQ,WAAM,GAAG;QACd,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;KAiBF;KACF,AAtBY,CAsBZ;IAWD,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC;YAC3C,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE;oBAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;gBAChC,CAAC;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE;aACpC;YACD,OAAO,EAAE;gBACP;oBACE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;oBACrC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;oBACtC,IAAI,EAAE,MAAM;iBACb;aACF;YACD,OAAO,EAAE,KAAK;SACf,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;gBAEC,IAAI,CAAC,IAAI;kBACP,IAAI,CAAC,MAAM;wBACL,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;kCAClB,IAAI,CAAC,yBAAyB,CAAC,UAAU,CAAE;;;8DAGf,IAAI,CAAC,OAAO;;;;QAIlE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,eAAe,OAAO,CAAC,CAAC,CAAC,mCAAmC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;KAC/F,CAAA;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,MAAM,GAAG;YACZ,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YAC5D,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC1C;oBACE,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,QAAQ;oBACpB,IAAI,EAAE,eAAe;oBACrB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;oBACjC,QAAQ,EAAE;wBACR,KAAK,EAAE,CAAC,QAAa,EAAE,KAAU,EAAE,OAAY,EAAE,MAAW,EAAE,EAAE;4BAC9D,IAAI,MAAM,EAAE,EAAE,EAAE,CAAC;gCACf,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;4BAC5B,CAAC;wBACH,CAAC;qBACF;iBACF;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;oBAC/B,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,iBAAiB;oBACvB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;oBACtC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;oBAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;oBAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC3B,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;oBACjC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;oBAC5C,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,UAAU;oBAChB,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC;oBACrC,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,GAAG;iBACX;aACF;SACF,CAAA;IACH,CAAC;IAED;;;;OAIG;IACK,UAAU,CAAC,YAAoB;QACrC,MAAM,KAAK,GAAG,SAAS,CACrB,IAAI,CAAA;wBACc,YAAY;+BACL,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;iBACtC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE;6BACP,EACvB;YACE,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;SAC3C,CACF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,QAAa,EAAE,UAAe;QAC9C,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,cAAc,CAAA;YACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAe;QAC1E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;OAkBT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;YAC7D,OAAO,EAAE,UAAU,EAAE;SACtB,CAAC,CAAA;QAEF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAA;QAEvF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB,OAAO;YACL,KAAK;YACL,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;SAChG,CAAA;IACH,CAAC;;AA9K4B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;2CAAwB;AACxB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2CAAY;AACX;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;yCAAoE;AAGlE;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;2CAAwB;AAEzB;IAA1B,KAAK,CAAC,UAAU,CAAC;;0CAA0B;AAhCjC,WAAW;IADvB,aAAa,CAAC,mBAAmB,CAAC;GACtB,WAAW,CAwMvB","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\nimport '@operato/context/ox-context-page-toolbar.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport '../../components/app-binding-detail.js'\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Applications bound to this domain.\n *\n * A binding is a `User` row with `userType: 'application'` — the account an application signs\n * in as. The list shows who it is and what it may do; what it signs in *with* is elsewhere.\n *\n * ── The server answers honestly now ──────────────────────────────────────────\n * Until dde62cd6a1 this query paged before it filtered: the database took a page of users of\n * every type, JavaScript kept the applications among them, and the total reported how many\n * survived that one page. A grid told there were three when there were thirty never offered a\n * second page. Wiring paging up was pointless until that was fixed; it works now.\n *\n * ── ⚠ The refresh token is not asked for, and a test holds that ──────────────\n * AppBinding's refresh token is the application's actual credential — oauth2-server.ts writes\n * it into the password column verbatim, not hashed — and the field carries its own\n * privilege directive for security:query.\n *\n * A credential that can be exchanged for access tokens does not belong in a grid cell, where\n * it sits in plain text beside a name for anyone who can open the screen, and in any\n * screenshot of it. `app-binding-list-query.test.ts` fails if the word appears here.\n */\n@customElement('app-bindings-page')\nexport class AppBindings extends p13n(localize(i18next)(PageView)) {\n static styles = [\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.bound applications'),\n help: 'auth/application',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('text.applications'),\n action: () => navigate('applications'),\n icon: 'apps'\n }\n ],\n toolbar: false\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <ox-context-page-toolbar class=\"actions\" .context=${this.context}></ox-context-page-toolbar>\n </div>\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'chevron_right',\n title: i18next.t('button.detail'),\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n this.openDetail(record.id)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n type: 'string',\n name: 'applicationName',\n header: i18next.t('label.application'),\n record: { editable: false },\n width: 180\n },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { editable: false },\n sortable: true,\n width: 220\n },\n {\n type: 'string',\n name: 'scope',\n header: i18next.t('field.scope'),\n record: { editable: false },\n width: 220\n },\n {\n type: 'string',\n name: 'status',\n header: i18next.t('field.status'),\n record: { align: 'center', editable: false },\n sortable: true,\n width: 130\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated at'),\n sortable: true,\n width: 180\n }\n ]\n }\n }\n\n /*\n * A binding is not authored — it is what an application ends up with after the OAuth flow —\n * so this popup shows one and offers the two things a person can do to it. `app-binding/{id}`\n * was a route; nothing outside auth-ui linked to it.\n */\n private openDetail(appBindingId: string) {\n const popup = openPopup(\n html`<app-binding-detail\n .appBindingId=${appBindingId}\n @app-binding-changed=${() => this.grist.fetch()}\n @close=${() => popup.close()}\n ></app-binding-detail>`,\n {\n backdrop: true,\n size: 'medium',\n title: i18next.t('text.bound application')\n }\n )\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n appBindings(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n name\n email\n status\n scope\n updatedAt\n application {\n id\n name\n }\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n context: gqlContext()\n })\n\n const { items, total, failed } = listOutcome(pageFromResponse(response, 'appBindings'))\n\n this.failed = failed\n\n return {\n total,\n records: items.map((item: any) => ({ ...item, applicationName: item.application?.name || '' }))\n }\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* A list query must not ask for a field that carries its own privilege gate.
|
|
5
|
+
*
|
|
6
|
+
* `Appliance.accessToken` is declared with
|
|
7
|
+
* `@privilege(category: "security", privilege: "query", domainOwnerGranted: true)` on the
|
|
8
|
+
* field. A viewer without that privilege who asks for it gets the rows **and** an error
|
|
9
|
+
* about the one field.
|
|
10
|
+
*
|
|
11
|
+
* ⚠ An earlier version of this comment said that would black the whole list out. It would
|
|
12
|
+
* have, until `pageFromResponse` learned to read the error's path and keep a page whose only
|
|
13
|
+
* complaint points at a value inside it. That reasoning is gone; the rule stands on its own
|
|
14
|
+
* ground instead:
|
|
15
|
+
*
|
|
16
|
+
* A token in a list is a token shown to everyone who may open the list, sitting in a grid
|
|
17
|
+
* cell, in plain text, next to a name. Nothing about the appliance list needs it, and the
|
|
18
|
+
* screens that do need it ask for it one record at a time. It is one word in a query and
|
|
19
|
+
* nothing else in the build objects to it, which is why a test holds it rather than a
|
|
20
|
+
* comment.
|
|
21
|
+
*
|
|
22
|
+
* ⚠ The comments are stripped before searching. The doc comment above names `accessToken`
|
|
23
|
+
* three times; searching the raw file would find the prose and pass over a query that
|
|
24
|
+
* really does ask for it. That is the defect this repository produced eight times in one
|
|
25
|
+
* day — a measurement that reads the same words as the thing it measures.
|
|
26
|
+
*/
|
|
27
|
+
function source(name) {
|
|
28
|
+
const text = readFileSync(join(__dirname, name), 'utf-8');
|
|
29
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^[ \t]*\/\/.*$/gm, '');
|
|
30
|
+
}
|
|
31
|
+
/** The fields Appliance declares with no gate of their own. */
|
|
32
|
+
const UNGATED = ['name', 'serialNo', 'brand', 'model', 'netmask', 'description', 'updatedAt'];
|
|
33
|
+
describe('the appliance list query', () => {
|
|
34
|
+
it('does not ask for the access token', () => {
|
|
35
|
+
expect(source('./home.ts')).not.toContain('accessToken');
|
|
36
|
+
});
|
|
37
|
+
it('asks for the fields the columns render', () => {
|
|
38
|
+
/*
|
|
39
|
+
* The other half. A column whose field is not requested renders empty for every row,
|
|
40
|
+
* which reads as "this appliance has no serial number" rather than "nobody asked".
|
|
41
|
+
*/
|
|
42
|
+
const text = source('./home.ts');
|
|
43
|
+
for (const field of UNGATED) {
|
|
44
|
+
expect(text).toContain(field);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it('passes the grist options through, so paging and sorting reach the server', () => {
|
|
48
|
+
/*
|
|
49
|
+
* The screen this replaced fetched everything on every visit and sorted nothing. The
|
|
50
|
+
* server takes ListParam; forgetting to pass it is invisible until the table is long.
|
|
51
|
+
*/
|
|
52
|
+
const text = source('./home.ts');
|
|
53
|
+
expect(text).toContain('$pagination: Pagination');
|
|
54
|
+
expect(text).toContain('$sortings: [Sorting!]');
|
|
55
|
+
expect(text).toContain('$filters: [Filter!]');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=appliance-list-query.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-list-query.test.js","sourceRoot":"","sources":["../../../client/pages/appliance/appliance-list-query.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;IAEzD,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;AAC9E,CAAC;AAED,+DAA+D;AAC/D,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAA;AAE7F,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD;;;WAGG;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;QAEhC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF;;;WAGG;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;QAEhC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAA;QACjD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAA;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { readFileSync } from 'fs'\nimport { join } from 'path'\n\n/**\n * A list query must not ask for a field that carries its own privilege gate.\n *\n * `Appliance.accessToken` is declared with\n * `@privilege(category: \"security\", privilege: \"query\", domainOwnerGranted: true)` on the\n * field. A viewer without that privilege who asks for it gets the rows **and** an error\n * about the one field.\n *\n * ⚠ An earlier version of this comment said that would black the whole list out. It would\n * have, until `pageFromResponse` learned to read the error's path and keep a page whose only\n * complaint points at a value inside it. That reasoning is gone; the rule stands on its own\n * ground instead:\n *\n * A token in a list is a token shown to everyone who may open the list, sitting in a grid\n * cell, in plain text, next to a name. Nothing about the appliance list needs it, and the\n * screens that do need it ask for it one record at a time. It is one word in a query and\n * nothing else in the build objects to it, which is why a test holds it rather than a\n * comment.\n *\n * ⚠ The comments are stripped before searching. The doc comment above names `accessToken`\n * three times; searching the raw file would find the prose and pass over a query that\n * really does ask for it. That is the defect this repository produced eight times in one\n * day — a measurement that reads the same words as the thing it measures.\n */\nfunction source(name: string): string {\n const text = readFileSync(join(__dirname, name), 'utf-8')\n\n return text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/^[ \\t]*\\/\\/.*$/gm, '')\n}\n\n/** The fields Appliance declares with no gate of their own. */\nconst UNGATED = ['name', 'serialNo', 'brand', 'model', 'netmask', 'description', 'updatedAt']\n\ndescribe('the appliance list query', () => {\n it('does not ask for the access token', () => {\n expect(source('./home.ts')).not.toContain('accessToken')\n })\n\n it('asks for the fields the columns render', () => {\n /*\n * The other half. A column whose field is not requested renders empty for every row,\n * which reads as \"this appliance has no serial number\" rather than \"nobody asked\".\n */\n const text = source('./home.ts')\n\n for (const field of UNGATED) {\n expect(text).toContain(field)\n }\n })\n\n it('passes the grist options through, so paging and sorting reach the server', () => {\n /*\n * The screen this replaced fetched everything on every visit and sorted nothing. The\n * server takes ListParam; forgetting to pass it is invisible until the table is long.\n */\n const text = source('./home.ts')\n\n expect(text).toContain('$pagination: Pagination')\n expect(text).toContain('$sortings: [Sorting!]')\n expect(text).toContain('$filters: [Filter!]')\n })\n})\n"]}
|
|
@@ -1,2 +1,63 @@
|
|
|
1
|
-
import '@material/web/
|
|
2
|
-
import '@
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@operato/data-grist/ox-grist.js';
|
|
3
|
+
import '@operato/context/ox-context-page-toolbar.js';
|
|
4
|
+
import type { FetchOption } from '@operato/data-grist';
|
|
5
|
+
import { PageView } from '@operato/shell';
|
|
6
|
+
import '../../components/appliance-editor.js';
|
|
7
|
+
declare const Appliances_base: (new (...args: any[]) => {
|
|
8
|
+
__preferenceProviders: {
|
|
9
|
+
[element: string]: import("@operato/p13n").PagePreferenceProvider;
|
|
10
|
+
};
|
|
11
|
+
getPagePreferenceProvider(element: string): import("@operato/p13n").PagePreferenceProvider | undefined;
|
|
12
|
+
}) & (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
13
|
+
/**
|
|
14
|
+
* Registered appliances.
|
|
15
|
+
*
|
|
16
|
+
* ── What this screen looked like before ──────────────────────────────────────
|
|
17
|
+
* A hand-written table with no paging, sorting or search, and three columns that were not
|
|
18
|
+
* data: "API health" printed OK, "Installs" printed 1, "status" printed draft, identical on
|
|
19
|
+
* every row. An appliance that had never checked in read exactly like one reporting fine.
|
|
20
|
+
* Those are gone — Appliance declares none of the three — and the columns here are the
|
|
21
|
+
* fields it does declare.
|
|
22
|
+
*
|
|
23
|
+
* ── ⚠ The access token is not asked for, and a test holds that ──────────────
|
|
24
|
+
* `Appliance.accessToken` carries its own
|
|
25
|
+
* `@privilege(category: "security", privilege: "query", domainOwnerGranted: true)`. A list
|
|
26
|
+
* is the wrong place for it regardless of who may read it: a token in a grid cell is a token
|
|
27
|
+
* in plain text beside a name, visible to everyone who can open the screen and to anyone
|
|
28
|
+
* looking over their shoulder. The screens that genuinely need it ask one record at a time.
|
|
29
|
+
*
|
|
30
|
+
* `appliance-list-query.test.ts` fails if the word reappears here.
|
|
31
|
+
*/
|
|
32
|
+
export declare class Appliances extends Appliances_base {
|
|
33
|
+
static styles: import("lit").CSSResult[];
|
|
34
|
+
active: boolean;
|
|
35
|
+
config: any;
|
|
36
|
+
mode: 'CARD' | 'LIST' | 'GRID';
|
|
37
|
+
/** The last fetch did not answer. Distinct from answering with nothing. */
|
|
38
|
+
failed: boolean;
|
|
39
|
+
private grist;
|
|
40
|
+
get context(): {
|
|
41
|
+
title: string;
|
|
42
|
+
help: string;
|
|
43
|
+
search: {
|
|
44
|
+
handler: (search: string) => void;
|
|
45
|
+
value: string;
|
|
46
|
+
};
|
|
47
|
+
actions: {
|
|
48
|
+
title: string;
|
|
49
|
+
action: () => void;
|
|
50
|
+
icon: string;
|
|
51
|
+
}[];
|
|
52
|
+
toolbar: boolean;
|
|
53
|
+
};
|
|
54
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
55
|
+
pageInitialized(): Promise<void>;
|
|
56
|
+
private openEditor;
|
|
57
|
+
pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
|
|
58
|
+
fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
|
|
59
|
+
total: number;
|
|
60
|
+
records: any[];
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export {};
|