@things-factory/auth-ui 10.1.225 → 10.1.226
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/identity-confirmation/identity-confirm-popup.d.ts +23 -0
- package/dist-client/identity-confirmation/identity-confirm-popup.js +121 -0
- package/dist-client/identity-confirmation/identity-confirm-popup.js.map +1 -0
- package/dist-client/identity-confirmation/identity-confirmation.d.ts +52 -0
- package/dist-client/identity-confirmation/identity-confirmation.js +148 -0
- package/dist-client/identity-confirmation/identity-confirmation.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/translations/en.json +9 -1
- package/translations/es.json +9 -1
- package/translations/ja.json +9 -1
- package/translations/ko.json +9 -1
- package/translations/ms.json +9 -1
- package/translations/zh.json +9 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import '@material/web/button/text-button.js';
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/outlined-button.js';
|
|
4
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
5
|
+
import { LitElement } from 'lit';
|
|
6
|
+
import type { CSSResultGroup } from 'lit';
|
|
7
|
+
declare const IdentityConfirmPopup_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
8
|
+
/**
|
|
9
|
+
* Confirming it is still the signed-in person before a door that asks so (ADR-0168): the password, or a passkey
|
|
10
|
+
* where the browser has them. The popup closes with `true` once the server took the confirmation; the server's own
|
|
11
|
+
* sentence says why a password was not taken - a miss and how many, a lock.
|
|
12
|
+
*/
|
|
13
|
+
export declare class IdentityConfirmPopup extends IdentityConfirmPopup_base {
|
|
14
|
+
static styles: CSSResultGroup;
|
|
15
|
+
private password;
|
|
16
|
+
private refused;
|
|
17
|
+
private busy;
|
|
18
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
19
|
+
firstUpdated(): void;
|
|
20
|
+
private byPassword;
|
|
21
|
+
private byPasskey;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/text-button.js';
|
|
3
|
+
import '@material/web/button/filled-button.js';
|
|
4
|
+
import '@material/web/button/outlined-button.js';
|
|
5
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
6
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
7
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
8
|
+
import { i18next, localize } from '@operato/i18n';
|
|
9
|
+
import { closePopup } from '@operato/popup';
|
|
10
|
+
import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
|
|
11
|
+
import { verifyBiometric } from '@things-factory/auth-base/dist-client';
|
|
12
|
+
/**
|
|
13
|
+
* Confirming it is still the signed-in person before a door that asks so (ADR-0168): the password, or a passkey
|
|
14
|
+
* where the browser has them. The popup closes with `true` once the server took the confirmation; the server's own
|
|
15
|
+
* sentence says why a password was not taken - a miss and how many, a lock.
|
|
16
|
+
*/
|
|
17
|
+
let IdentityConfirmPopup = class IdentityConfirmPopup extends localize(i18next)(LitElement) {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.password = '';
|
|
21
|
+
this.refused = '';
|
|
22
|
+
this.busy = false;
|
|
23
|
+
}
|
|
24
|
+
static { this.styles = [
|
|
25
|
+
CommonPopupStyles,
|
|
26
|
+
PopupBodyStyles,
|
|
27
|
+
FormFieldStyles,
|
|
28
|
+
PopupFieldStyles,
|
|
29
|
+
css `
|
|
30
|
+
.note {
|
|
31
|
+
color: var(--ui-muted);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.failure {
|
|
35
|
+
color: var(--md-sys-color-error);
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
]; }
|
|
39
|
+
render() {
|
|
40
|
+
const passkeys = typeof window !== 'undefined' && 'PublicKeyCredential' in window;
|
|
41
|
+
return html `
|
|
42
|
+
<form class="body" @submit=${(e) => (e.preventDefault(), this.byPassword())}>
|
|
43
|
+
<div class="note">${i18next.t('text.identity confirmation why')}</div>
|
|
44
|
+
<label class="field">
|
|
45
|
+
<span>${i18next.t('field.password')}</span>
|
|
46
|
+
<input
|
|
47
|
+
type="password"
|
|
48
|
+
name="password"
|
|
49
|
+
autocomplete="current-password"
|
|
50
|
+
.value=${this.password}
|
|
51
|
+
@input=${(e) => ((this.password = e.target.value), (this.refused = ''))}
|
|
52
|
+
/>
|
|
53
|
+
</label>
|
|
54
|
+
${this.refused ? html `<div class="failure" role="alert">${this.refused}</div>` : nothing}
|
|
55
|
+
</form>
|
|
56
|
+
<ox-popup-actions>
|
|
57
|
+
${passkeys
|
|
58
|
+
? html `<md-outlined-button slot="start" ?disabled=${this.busy} @click=${() => this.byPasskey()}>
|
|
59
|
+
${i18next.t('button.confirm with passkey')}
|
|
60
|
+
</md-outlined-button>`
|
|
61
|
+
: nothing}
|
|
62
|
+
<md-text-button slot="cancel" @click=${() => closePopup(this, false)}>${i18next.t('button.cancel')}</md-text-button>
|
|
63
|
+
<md-filled-button slot="confirm" ?disabled=${this.busy || !this.password} @click=${() => this.byPassword()}>
|
|
64
|
+
${i18next.t('button.confirm')}
|
|
65
|
+
</md-filled-button>
|
|
66
|
+
</ox-popup-actions>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
firstUpdated() {
|
|
70
|
+
;
|
|
71
|
+
this.renderRoot.querySelector('input[name=password]')?.focus();
|
|
72
|
+
}
|
|
73
|
+
async byPassword() {
|
|
74
|
+
if (!this.password || this.busy)
|
|
75
|
+
return;
|
|
76
|
+
this.busy = true;
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch('/auth/confirm-identity', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
81
|
+
body: JSON.stringify({ password: this.password })
|
|
82
|
+
});
|
|
83
|
+
if (response.ok)
|
|
84
|
+
return closePopup(this, true);
|
|
85
|
+
this.password = '';
|
|
86
|
+
this.refused = (await response.text()) || i18next.t('text.identity not confirmed');
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
this.busy = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async byPasskey() {
|
|
93
|
+
this.busy = true;
|
|
94
|
+
try {
|
|
95
|
+
const result = await verifyBiometric();
|
|
96
|
+
if (result.verified)
|
|
97
|
+
return closePopup(this, true);
|
|
98
|
+
this.refused = i18next.t(result.needsRegistration ? 'text.no passkey to confirm with' : 'text.identity not confirmed');
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
this.busy = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
__decorate([
|
|
106
|
+
state(),
|
|
107
|
+
__metadata("design:type", Object)
|
|
108
|
+
], IdentityConfirmPopup.prototype, "password", void 0);
|
|
109
|
+
__decorate([
|
|
110
|
+
state(),
|
|
111
|
+
__metadata("design:type", Object)
|
|
112
|
+
], IdentityConfirmPopup.prototype, "refused", void 0);
|
|
113
|
+
__decorate([
|
|
114
|
+
state(),
|
|
115
|
+
__metadata("design:type", Object)
|
|
116
|
+
], IdentityConfirmPopup.prototype, "busy", void 0);
|
|
117
|
+
IdentityConfirmPopup = __decorate([
|
|
118
|
+
customElement('identity-confirm-popup')
|
|
119
|
+
], IdentityConfirmPopup);
|
|
120
|
+
export { IdentityConfirmPopup };
|
|
121
|
+
//# sourceMappingURL=identity-confirm-popup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-confirm-popup.js","sourceRoot":"","sources":["../../client/identity-confirmation/identity-confirm-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/outlined-button.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport type { CSSResultGroup } from 'lit'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { closePopup } from '@operato/popup'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\nimport { verifyBiometric } from '@things-factory/auth-base/dist-client'\n\n/**\n * Confirming it is still the signed-in person before a door that asks so (ADR-0168): the password, or a passkey\n * where the browser has them. The popup closes with `true` once the server took the confirmation; the server's own\n * sentence says why a password was not taken - a miss and how many, a lock.\n */\n@customElement('identity-confirm-popup')\nexport class IdentityConfirmPopup extends localize(i18next)(LitElement) {\n static styles: CSSResultGroup = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n .note {\n color: var(--ui-muted);\n }\n\n .failure {\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n @state() private password = ''\n @state() private refused = ''\n @state() private busy = false\n\n render() {\n const passkeys = typeof window !== 'undefined' && 'PublicKeyCredential' in window\n\n return html`\n <form class=\"body\" @submit=${(e: Event) => (e.preventDefault(), this.byPassword())}>\n <div class=\"note\">${i18next.t('text.identity confirmation why')}</div>\n <label class=\"field\">\n <span>${i18next.t('field.password')}</span>\n <input\n type=\"password\"\n name=\"password\"\n autocomplete=\"current-password\"\n .value=${this.password}\n @input=${(e: InputEvent) => ((this.password = (e.target as HTMLInputElement).value), (this.refused = ''))}\n />\n </label>\n ${this.refused ? html`<div class=\"failure\" role=\"alert\">${this.refused}</div>` : nothing}\n </form>\n <ox-popup-actions>\n ${passkeys\n ? html`<md-outlined-button slot=\"start\" ?disabled=${this.busy} @click=${() => this.byPasskey()}>\n ${i18next.t('button.confirm with passkey')}\n </md-outlined-button>`\n : nothing}\n <md-text-button slot=\"cancel\" @click=${() => closePopup(this, false)}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.busy || !this.password} @click=${() => this.byPassword()}>\n ${i18next.t('button.confirm')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n firstUpdated() {\n ;(this.renderRoot.querySelector('input[name=password]') as HTMLInputElement | null)?.focus()\n }\n\n private async byPassword() {\n if (!this.password || this.busy) return\n this.busy = true\n try {\n const response = await fetch('/auth/confirm-identity', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({ password: this.password })\n })\n if (response.ok) return closePopup(this, true)\n\n this.password = ''\n this.refused = (await response.text()) || i18next.t('text.identity not confirmed')\n } finally {\n this.busy = false\n }\n }\n\n private async byPasskey() {\n this.busy = true\n try {\n const result = await verifyBiometric()\n if (result.verified) return closePopup(this, true)\n\n this.refused = i18next.t(result.needsRegistration ? 'text.no passkey to confirm with' : 'text.identity not confirmed')\n } finally {\n this.busy = false\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import './identity-confirm-popup.js';
|
|
2
|
+
import '@material/web/button/outlined-button.js';
|
|
3
|
+
import { LitElement } from 'lit';
|
|
4
|
+
import type { ReactiveController, ReactiveControllerHost, TemplateResult } from 'lit';
|
|
5
|
+
/** The server's refusal code for a door that asks for a recent confirmation. */
|
|
6
|
+
export declare const IDENTITY_CONFIRMATION_REFUSAL = "identity-confirmation-required";
|
|
7
|
+
/** The query context for such a door: the screen asks for the confirmation itself, so no error snackbar. */
|
|
8
|
+
export declare const CONFIRMED_IDENTITY_CONTEXT: {
|
|
9
|
+
handledRefusals: string[];
|
|
10
|
+
};
|
|
11
|
+
/** Whether `error` - an Apollo error, or a response with `errors` - is that refusal. */
|
|
12
|
+
export declare function needsIdentityConfirmation(error: any): boolean;
|
|
13
|
+
/** Ask the person to confirm it is them - true once the server took it. Two screens asking at once share one popup. */
|
|
14
|
+
export declare function confirmIdentity(): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Run `read`; when the door refuses for want of a recent confirmation, ask for it and read once more. Undefined when
|
|
17
|
+
* the person did not confirm - the screen then says so (`identity-confirmation-notice`) rather than an error.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withConfirmedIdentity<T>(read: () => Promise<T>): Promise<T | undefined>;
|
|
20
|
+
/** Until when the confirmation in this browser holds - null when there is none (ADR-0168 decision 3). */
|
|
21
|
+
export declare function confirmedUntil(): Promise<Date | null>;
|
|
22
|
+
declare const IdentityConfirmationNotice_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
23
|
+
/**
|
|
24
|
+
* What a screen shows in place of what it would not read: the confirmation was declined, or it ran out and what the
|
|
25
|
+
* screen showed was put away. Its button asks again and fires `confirmed` once the person did.
|
|
26
|
+
*/
|
|
27
|
+
export declare class IdentityConfirmationNotice extends IdentityConfirmationNotice_base {
|
|
28
|
+
static styles: import("lit").CSSResult;
|
|
29
|
+
/** The confirmation ran out and what the screen showed was put away - rather than a confirmation not yet given. */
|
|
30
|
+
expired: boolean;
|
|
31
|
+
render(): TemplateResult<1>;
|
|
32
|
+
private ask;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A screen's hold on what a confirmation opened: it reads through `read`, which asks when the door does, and puts
|
|
36
|
+
* what it read away (`onPutAway` drops the screen's data) the moment the confirmation ends - the screen does not
|
|
37
|
+
* keep showing it past ten minutes. `unconfirmed` says why nothing is shown - declined, or ran out - and `notice`
|
|
38
|
+
* draws that, with the way to ask again. A screen that goes away drops its data by calling `putAway()` itself.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ConfirmedIdentityController implements ReactiveController {
|
|
41
|
+
private host;
|
|
42
|
+
private onPutAway;
|
|
43
|
+
unconfirmed: 'declined' | 'expired' | null;
|
|
44
|
+
private timer?;
|
|
45
|
+
constructor(host: ReactiveControllerHost, onPutAway: () => void);
|
|
46
|
+
read<T>(run: () => Promise<T>): Promise<T | undefined>;
|
|
47
|
+
/** Drop what the screen shows; `reason` is said in its place, none when the screen only went away. */
|
|
48
|
+
putAway(reason?: 'declined' | 'expired' | null): void;
|
|
49
|
+
notice(again: () => void): TemplateResult;
|
|
50
|
+
hostDisconnected(): void;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import './identity-confirm-popup.js';
|
|
3
|
+
import '@material/web/button/outlined-button.js';
|
|
4
|
+
import { css, html, LitElement } from 'lit';
|
|
5
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
6
|
+
import { i18next, localize } from '@operato/i18n';
|
|
7
|
+
import { openPopup } from '@operato/layout';
|
|
8
|
+
/*
|
|
9
|
+
* ── Screens of a door that asks for a recent confirmation (ADR-0168) ─────────────────────
|
|
10
|
+
* The server refuses such a door until the person confirmed it is them in this browser, within the last ten minutes.
|
|
11
|
+
* A screen reads through `ConfirmedIdentityController`, which asks once and reads again, and puts away what it shows
|
|
12
|
+
* the moment the confirmation ends - a screen kept open past ten minutes asks again (the user, 2026-10-28). One way
|
|
13
|
+
* for every app - none draws its own password box.
|
|
14
|
+
*/
|
|
15
|
+
/** The server's refusal code for a door that asks for a recent confirmation. */
|
|
16
|
+
export const IDENTITY_CONFIRMATION_REFUSAL = 'identity-confirmation-required';
|
|
17
|
+
/** The query context for such a door: the screen asks for the confirmation itself, so no error snackbar. */
|
|
18
|
+
export const CONFIRMED_IDENTITY_CONTEXT = { handledRefusals: [IDENTITY_CONFIRMATION_REFUSAL] };
|
|
19
|
+
/** Whether `error` - an Apollo error, or a response with `errors` - is that refusal. */
|
|
20
|
+
export function needsIdentityConfirmation(error) {
|
|
21
|
+
const errors = error?.graphQLErrors?.length ? error.graphQLErrors : (error?.errors ?? []);
|
|
22
|
+
return errors.some((one) => one?.extensions?.refusal?.code === IDENTITY_CONFIRMATION_REFUSAL);
|
|
23
|
+
}
|
|
24
|
+
let asking = null;
|
|
25
|
+
/** Ask the person to confirm it is them - true once the server took it. Two screens asking at once share one popup. */
|
|
26
|
+
export function confirmIdentity() {
|
|
27
|
+
if (!asking) {
|
|
28
|
+
const popup = openPopup(html `<identity-confirm-popup></identity-confirm-popup>`, {
|
|
29
|
+
backdrop: true,
|
|
30
|
+
/* A form of one field stands at its own height - a fraction of the screen clipped it and left half the card empty. */
|
|
31
|
+
size: 'fit',
|
|
32
|
+
title: i18next.t('title.confirm it is you')
|
|
33
|
+
});
|
|
34
|
+
asking = popup.result.then(result => result === true).finally(() => (asking = null));
|
|
35
|
+
}
|
|
36
|
+
return asking;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run `read`; when the door refuses for want of a recent confirmation, ask for it and read once more. Undefined when
|
|
40
|
+
* the person did not confirm - the screen then says so (`identity-confirmation-notice`) rather than an error.
|
|
41
|
+
*/
|
|
42
|
+
export async function withConfirmedIdentity(read) {
|
|
43
|
+
try {
|
|
44
|
+
return await read();
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!needsIdentityConfirmation(error))
|
|
48
|
+
throw error;
|
|
49
|
+
if (!(await confirmIdentity()))
|
|
50
|
+
return undefined;
|
|
51
|
+
return await read();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Until when the confirmation in this browser holds - null when there is none (ADR-0168 decision 3). */
|
|
55
|
+
export async function confirmedUntil() {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch('/auth/confirm-identity', { headers: { Accept: 'application/json' } });
|
|
58
|
+
const { confirmedUntil } = response.ok ? await response.json() : { confirmedUntil: null };
|
|
59
|
+
return confirmedUntil ? new Date(confirmedUntil) : null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What a screen shows in place of what it would not read: the confirmation was declined, or it ran out and what the
|
|
67
|
+
* screen showed was put away. Its button asks again and fires `confirmed` once the person did.
|
|
68
|
+
*/
|
|
69
|
+
let IdentityConfirmationNotice = class IdentityConfirmationNotice extends localize(i18next)(LitElement) {
|
|
70
|
+
constructor() {
|
|
71
|
+
super(...arguments);
|
|
72
|
+
/** The confirmation ran out and what the screen showed was put away - rather than a confirmation not yet given. */
|
|
73
|
+
this.expired = false;
|
|
74
|
+
}
|
|
75
|
+
static { this.styles = css `
|
|
76
|
+
:host {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
align-items: flex-start;
|
|
80
|
+
gap: var(--spacing-medium, 8px);
|
|
81
|
+
padding: var(--spacing-large, 12px) 0;
|
|
82
|
+
color: var(--ui-muted);
|
|
83
|
+
}
|
|
84
|
+
`; }
|
|
85
|
+
render() {
|
|
86
|
+
return html `
|
|
87
|
+
<span>${i18next.t(this.expired ? 'text.identity confirmation expired' : 'text.identity confirmation needed')}</span>
|
|
88
|
+
<md-outlined-button @click=${() => this.ask()}>${i18next.t('button.confirm it is you')}</md-outlined-button>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
async ask() {
|
|
92
|
+
if (await confirmIdentity())
|
|
93
|
+
this.dispatchEvent(new CustomEvent('confirmed', { bubbles: true, composed: true }));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
__decorate([
|
|
97
|
+
property({ type: Boolean }),
|
|
98
|
+
__metadata("design:type", Object)
|
|
99
|
+
], IdentityConfirmationNotice.prototype, "expired", void 0);
|
|
100
|
+
IdentityConfirmationNotice = __decorate([
|
|
101
|
+
customElement('identity-confirmation-notice')
|
|
102
|
+
], IdentityConfirmationNotice);
|
|
103
|
+
export { IdentityConfirmationNotice };
|
|
104
|
+
/**
|
|
105
|
+
* A screen's hold on what a confirmation opened: it reads through `read`, which asks when the door does, and puts
|
|
106
|
+
* what it read away (`onPutAway` drops the screen's data) the moment the confirmation ends - the screen does not
|
|
107
|
+
* keep showing it past ten minutes. `unconfirmed` says why nothing is shown - declined, or ran out - and `notice`
|
|
108
|
+
* draws that, with the way to ask again. A screen that goes away drops its data by calling `putAway()` itself.
|
|
109
|
+
*/
|
|
110
|
+
export class ConfirmedIdentityController {
|
|
111
|
+
constructor(host, onPutAway) {
|
|
112
|
+
this.host = host;
|
|
113
|
+
this.onPutAway = onPutAway;
|
|
114
|
+
this.unconfirmed = null;
|
|
115
|
+
host.addController(this);
|
|
116
|
+
}
|
|
117
|
+
async read(run) {
|
|
118
|
+
const answer = await withConfirmedIdentity(run);
|
|
119
|
+
if (answer === undefined) {
|
|
120
|
+
this.putAway('declined');
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
this.unconfirmed = null;
|
|
124
|
+
clearTimeout(this.timer);
|
|
125
|
+
const until = await confirmedUntil();
|
|
126
|
+
/* The read just passed the door, so a confirmation is there; not knowing until when, the screen does not keep it. */
|
|
127
|
+
const left = until ? until.getTime() - Date.now() : 0;
|
|
128
|
+
this.timer = setTimeout(() => this.putAway('expired'), Math.max(left, 0));
|
|
129
|
+
this.host.requestUpdate();
|
|
130
|
+
return answer;
|
|
131
|
+
}
|
|
132
|
+
/** Drop what the screen shows; `reason` is said in its place, none when the screen only went away. */
|
|
133
|
+
putAway(reason = null) {
|
|
134
|
+
clearTimeout(this.timer);
|
|
135
|
+
this.timer = undefined;
|
|
136
|
+
this.unconfirmed = reason;
|
|
137
|
+
this.onPutAway();
|
|
138
|
+
this.host.requestUpdate();
|
|
139
|
+
}
|
|
140
|
+
notice(again) {
|
|
141
|
+
return html `<identity-confirmation-notice ?expired=${this.unconfirmed === 'expired'} @confirmed=${again}></identity-confirmation-notice>`;
|
|
142
|
+
}
|
|
143
|
+
hostDisconnected() {
|
|
144
|
+
clearTimeout(this.timer);
|
|
145
|
+
this.timer = undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=identity-confirmation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-confirmation.js","sourceRoot":"","sources":["../../client/identity-confirmation/identity-confirmation.ts"],"names":[],"mappings":"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","sourcesContent":["import './identity-confirm-popup.js'\nimport '@material/web/button/outlined-button.js'\n\nimport { css, html, LitElement } from 'lit'\nimport type { ReactiveController, ReactiveControllerHost, TemplateResult } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\n\n/*\n * ── Screens of a door that asks for a recent confirmation (ADR-0168) ─────────────────────\n * The server refuses such a door until the person confirmed it is them in this browser, within the last ten minutes.\n * A screen reads through `ConfirmedIdentityController`, which asks once and reads again, and puts away what it shows\n * the moment the confirmation ends - a screen kept open past ten minutes asks again (the user, 2026-10-28). One way\n * for every app - none draws its own password box.\n */\n\n/** The server's refusal code for a door that asks for a recent confirmation. */\nexport const IDENTITY_CONFIRMATION_REFUSAL = 'identity-confirmation-required'\n\n/** The query context for such a door: the screen asks for the confirmation itself, so no error snackbar. */\nexport const CONFIRMED_IDENTITY_CONTEXT = { handledRefusals: [IDENTITY_CONFIRMATION_REFUSAL] }\n\n/** Whether `error` - an Apollo error, or a response with `errors` - is that refusal. */\nexport function needsIdentityConfirmation(error: any): boolean {\n const errors = error?.graphQLErrors?.length ? error.graphQLErrors : (error?.errors ?? [])\n return errors.some((one: any) => one?.extensions?.refusal?.code === IDENTITY_CONFIRMATION_REFUSAL)\n}\n\nlet asking: Promise<boolean> | null = null\n\n/** Ask the person to confirm it is them - true once the server took it. Two screens asking at once share one popup. */\nexport function confirmIdentity(): Promise<boolean> {\n if (!asking) {\n const popup = openPopup<boolean>(html`<identity-confirm-popup></identity-confirm-popup>`, {\n backdrop: true,\n /* A form of one field stands at its own height - a fraction of the screen clipped it and left half the card empty. */\n size: 'fit',\n title: i18next.t('title.confirm it is you')\n })\n asking = popup.result.then(result => result === true).finally(() => (asking = null))\n }\n return asking\n}\n\n/**\n * Run `read`; when the door refuses for want of a recent confirmation, ask for it and read once more. Undefined when\n * the person did not confirm - the screen then says so (`identity-confirmation-notice`) rather than an error.\n */\nexport async function withConfirmedIdentity<T>(read: () => Promise<T>): Promise<T | undefined> {\n try {\n return await read()\n } catch (error) {\n if (!needsIdentityConfirmation(error)) throw error\n if (!(await confirmIdentity())) return undefined\n return await read()\n }\n}\n\n/** Until when the confirmation in this browser holds - null when there is none (ADR-0168 decision 3). */\nexport async function confirmedUntil(): Promise<Date | null> {\n try {\n const response = await fetch('/auth/confirm-identity', { headers: { Accept: 'application/json' } })\n const { confirmedUntil } = response.ok ? await response.json() : { confirmedUntil: null }\n return confirmedUntil ? new Date(confirmedUntil) : null\n } catch {\n return null\n }\n}\n\n/**\n * What a screen shows in place of what it would not read: the confirmation was declined, or it ran out and what the\n * screen showed was put away. Its button asks again and fires `confirmed` once the person did.\n */\n@customElement('identity-confirmation-notice')\nexport class IdentityConfirmationNotice extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px) 0;\n color: var(--ui-muted);\n }\n `\n\n /** The confirmation ran out and what the screen showed was put away - rather than a confirmation not yet given. */\n @property({ type: Boolean }) expired = false\n\n render() {\n return html`\n <span>${i18next.t(this.expired ? 'text.identity confirmation expired' : 'text.identity confirmation needed')}</span>\n <md-outlined-button @click=${() => this.ask()}>${i18next.t('button.confirm it is you')}</md-outlined-button>\n `\n }\n\n private async ask() {\n if (await confirmIdentity()) this.dispatchEvent(new CustomEvent('confirmed', { bubbles: true, composed: true }))\n }\n}\n\n/**\n * A screen's hold on what a confirmation opened: it reads through `read`, which asks when the door does, and puts\n * what it read away (`onPutAway` drops the screen's data) the moment the confirmation ends - the screen does not\n * keep showing it past ten minutes. `unconfirmed` says why nothing is shown - declined, or ran out - and `notice`\n * draws that, with the way to ask again. A screen that goes away drops its data by calling `putAway()` itself.\n */\nexport class ConfirmedIdentityController implements ReactiveController {\n unconfirmed: 'declined' | 'expired' | null = null\n\n private timer?: ReturnType<typeof setTimeout>\n\n constructor(\n private host: ReactiveControllerHost,\n private onPutAway: () => void\n ) {\n host.addController(this)\n }\n\n async read<T>(run: () => Promise<T>): Promise<T | undefined> {\n const answer = await withConfirmedIdentity(run)\n if (answer === undefined) {\n this.putAway('declined')\n return undefined\n }\n\n this.unconfirmed = null\n clearTimeout(this.timer)\n const until = await confirmedUntil()\n /* The read just passed the door, so a confirmation is there; not knowing until when, the screen does not keep it. */\n const left = until ? until.getTime() - Date.now() : 0\n this.timer = setTimeout(() => this.putAway('expired'), Math.max(left, 0))\n this.host.requestUpdate()\n return answer\n }\n\n /** Drop what the screen shows; `reason` is said in its place, none when the screen only went away. */\n putAway(reason: 'declined' | 'expired' | null = null) {\n clearTimeout(this.timer)\n this.timer = undefined\n this.unconfirmed = reason\n this.onPutAway()\n this.host.requestUpdate()\n }\n\n notice(again: () => void): TemplateResult {\n return html`<identity-confirmation-notice ?expired=${this.unconfirmed === 'expired'} @confirmed=${again}></identity-confirmation-notice>`\n }\n\n hostDisconnected() {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n}\n"]}
|
package/dist-client/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import '@operato/i18n/ox-i18n.js';
|
|
|
3
3
|
export * from './components/role-selector.js';
|
|
4
4
|
export * from './components/domain-switch.js';
|
|
5
5
|
export * from './components/invite-user.js';
|
|
6
|
+
export * from './identity-confirmation/identity-confirmation.js';
|
|
6
7
|
export type AuthManagementMenuOptions = {
|
|
7
8
|
role?: boolean;
|
|
8
9
|
appliance?: boolean;
|
package/dist-client/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { hasPrivilege } from '@things-factory/auth-base/dist-client';
|
|
|
7
7
|
export * from './components/role-selector.js';
|
|
8
8
|
export * from './components/domain-switch.js';
|
|
9
9
|
export * from './components/invite-user.js';
|
|
10
|
+
/* A door that asks for a recent confirmation that it is still the person (ADR-0168) - one way for every screen. */
|
|
11
|
+
export * from './identity-confirmation/identity-confirmation.js';
|
|
10
12
|
export async function setAuthManagementMenus(options) {
|
|
11
13
|
const { role = true, appliance = true, application = true, user = true, authProvider = true, domain = true, attribute = true, audit = true } = options || {};
|
|
12
14
|
if (await hasPrivilege({ privilege: 'mutation', category: 'user', domainOwnerGranted: true, superUserGranted: true })) {
|
package/dist-client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAE/B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AAEpE,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { html } from 'lit-html'\n\nimport { store } from '@operato/shell'\nimport { ADD_MORENDA } from '@things-factory/more-base/client'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\nexport * from './components/role-selector.js'\nexport * from './components/domain-switch.js'\nexport * from './components/invite-user.js'\n/* A door that asks for a recent confirmation that it is still the person (ADR-0168) - one way for every screen. */\nexport * from './identity-confirmation/identity-confirmation.js'\n\nexport type AuthManagementMenuOptions = {\n role?: boolean\n appliance?: boolean\n application?: boolean\n user?: boolean\n authProvider?: boolean\n domain?: boolean\n attribute?: boolean\n audit?: boolean\n}\n\nexport async function setAuthManagementMenus(options?: any) {\n const {\n role = true,\n appliance = true,\n application = true,\n user = true,\n authProvider = true,\n domain = true,\n attribute = true,\n audit = true\n } = options || {}\n\n if (\n await hasPrivilege({ privilege: 'mutation', category: 'user', domainOwnerGranted: true, superUserGranted: true })\n ) {\n role &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>how_to_reg</md-icon> `,\n name: html` <ox-i18n msgid=\"text.role_management\"></ox-i18n> `,\n page: 'roles'\n }\n })\n\n appliance &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>devices</md-icon> `,\n name: html` <ox-i18n msgid=\"text.appliance\"></ox-i18n> `,\n page: 'appliance-home'\n }\n })\n\n application &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>apps</md-icon> `,\n name: html` <ox-i18n msgid=\"text.application management\"></ox-i18n> `,\n page: 'applications'\n }\n })\n\n user &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>people</md-icon> `,\n name: html` <ox-i18n msgid=\"text.user management\"></ox-i18n> `,\n page: 'users'\n }\n })\n\n authProvider &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>id_card</md-icon> `,\n name: html` <ox-i18n msgid=\"text.auth-provider management\"></ox-i18n> `,\n page: 'auth-providers'\n }\n })\n }\n\n /* The audit asks its own privilege - the domain owner does not pass on ownership (ADR-0141 §5). */\n if (audit && (await hasPrivilege({ privilege: 'query', category: 'audit', superUserGranted: true }))) {\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>policy</md-icon> `,\n name: html` <ox-i18n msgid=\"audit.title\"></ox-i18n> `,\n page: 'audit'\n }\n })\n }\n\n if (await hasPrivilege({ domainOwnerGranted: true, superUserGranted: true })) {\n domain &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>supervised_user_circle</md-icon> `,\n name: html` <ox-i18n msgid=\"text.domain owners\"></ox-i18n> `,\n page: 'domain-owners'\n }\n })\n }\n\n if (await hasPrivilege({ superUserGranted: true })) {\n domain &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>business</md-icon> `,\n name: html` <ox-i18n msgid=\"text.domain management\"></ox-i18n> `,\n page: 'domains'\n }\n })\n\n attribute &&\n store.dispatch({\n type: ADD_MORENDA,\n morenda: {\n icon: html` <md-icon>dataset</md-icon> `,\n name: html` <ox-i18n msgid=\"text.attribute management\"></ox-i18n> `,\n page: 'attributes'\n }\n })\n }\n}\n"]}
|