@things-factory/auth-ui 7.0.1-rc.1 → 7.0.1-rc.10
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/client/auth-style-sign.ts +2 -1
- package/client/components/abstract-sign.ts +30 -72
- package/client/components/profile-component.ts +27 -87
- package/client/entries/auth/activate.ts +2 -1
- package/client/entries/auth/checkin.ts +5 -2
- package/client/entries/auth/result.ts +2 -1
- package/dist-client/auth-style-sign.js +2 -1
- package/dist-client/auth-style-sign.js.map +1 -1
- package/dist-client/components/abstract-sign.d.ts +1 -1
- package/dist-client/components/abstract-sign.js +26 -60
- package/dist-client/components/abstract-sign.js.map +1 -1
- package/dist-client/components/profile-component.d.ts +1 -1
- package/dist-client/components/profile-component.js +25 -76
- package/dist-client/components/profile-component.js.map +1 -1
- package/dist-client/entries/auth/activate.js +2 -1
- package/dist-client/entries/auth/activate.js.map +1 -1
- package/dist-client/entries/auth/checkin.js +5 -2
- package/dist-client/entries/auth/checkin.js.map +1 -1
- package/dist-client/entries/auth/result.js +2 -1
- package/dist-client/entries/auth/result.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -3,7 +3,8 @@ import { css } from 'lit'
|
|
|
3
3
|
export const AUTH_STYLE_SIGN = css`
|
|
4
4
|
:host {
|
|
5
5
|
display: flex;
|
|
6
|
-
background-color: var(--
|
|
6
|
+
background-color: var(--md-sys-color-primary);
|
|
7
|
+
color: var(--md-sys-color-on-primary);
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
:host *:focus {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { html, nothing } from 'lit'
|
|
2
|
-
import base64url from 'base64url'
|
|
3
|
-
|
|
4
1
|
import '@operato/i18n/ox-i18n.js'
|
|
5
2
|
|
|
3
|
+
import { html, nothing } from 'lit'
|
|
4
|
+
import { startAuthentication } from '@simplewebauthn/browser'
|
|
5
|
+
|
|
6
6
|
import { i18next } from '@operato/i18n'
|
|
7
7
|
import { notify } from '@operato/layout'
|
|
8
8
|
|
|
@@ -85,84 +85,42 @@ export abstract class AbstractSign extends AbstractAuthPage {
|
|
|
85
85
|
<ox-i18n msgid="field.${this.pageName}"> </ox-i18n>
|
|
86
86
|
</md-elevated-button>
|
|
87
87
|
${isAvailableWebauthn
|
|
88
|
-
? html` <md-icon class="fingerprint" raised @click=${e => this.
|
|
88
|
+
? html` <md-icon class="fingerprint" raised @click=${e => this.authenticateUser()}> fingerprint </md-icon>`
|
|
89
89
|
: nothing}
|
|
90
90
|
</div>
|
|
91
91
|
`
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
async
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
})) as PublicKeyCredential
|
|
119
|
-
|
|
120
|
-
if (!credential) {
|
|
121
|
-
notify({
|
|
122
|
-
level: 'error',
|
|
123
|
-
message: 'can not get user credential'
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const assertionResponse = {
|
|
130
|
-
id: credential.id,
|
|
131
|
-
response: {
|
|
132
|
-
clientDataJSON: base64url.encode(Buffer.from(credential.response.clientDataJSON)),
|
|
133
|
-
authenticatorData: base64url.encode((credential.response as any).authenticatorData),
|
|
134
|
-
signature: base64url.encode((credential.response as any).signature),
|
|
135
|
-
userHandle: (credential.response as any).userHandle
|
|
136
|
-
? base64url.encode((credential.response as any).userHandle)
|
|
137
|
-
: null
|
|
94
|
+
async authenticateUser() {
|
|
95
|
+
try {
|
|
96
|
+
const options = await fetch('/auth/signin-webauthn/challenge').then(res => res.json())
|
|
97
|
+
const assertionResp = await startAuthentication(options)
|
|
98
|
+
const verification = await fetch('/auth/signin-webauthn', {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json'
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(assertionResp)
|
|
104
|
+
}).then(res => res.json())
|
|
105
|
+
|
|
106
|
+
if (verification.verified) {
|
|
107
|
+
const { redirectURL } = verification
|
|
108
|
+
|
|
109
|
+
if (redirectURL) {
|
|
110
|
+
window.location.href = redirectURL
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
notify({
|
|
114
|
+
level: 'error',
|
|
115
|
+
message: verification.message
|
|
116
|
+
})
|
|
138
117
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (credential.authenticatorAttachment) {
|
|
142
|
-
assertionResponse.authenticatorAttachment = credential.authenticatorAttachment as AuthenticatorAttachment
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const signinResponse = await fetch('/auth/signin-webauthn', {
|
|
146
|
-
method: 'POST',
|
|
147
|
-
headers: {
|
|
148
|
-
'Content-Type': 'application/json',
|
|
149
|
-
Accept: 'application/json'
|
|
150
|
-
},
|
|
151
|
-
body: JSON.stringify(assertionResponse),
|
|
152
|
-
credentials: 'include'
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
if (!signinResponse.ok) {
|
|
118
|
+
|
|
119
|
+
} catch (error) {
|
|
156
120
|
notify({
|
|
157
121
|
level: 'error',
|
|
158
|
-
message:
|
|
122
|
+
message: i18next.t('error.authn verification failed')
|
|
159
123
|
})
|
|
160
|
-
} else {
|
|
161
|
-
const { redirectURL } = await signinResponse.json()
|
|
162
|
-
|
|
163
|
-
if (redirectURL) {
|
|
164
|
-
window.location.href = redirectURL
|
|
165
|
-
}
|
|
166
124
|
}
|
|
167
125
|
}
|
|
168
126
|
}
|
|
@@ -7,6 +7,7 @@ import './my-login-history'
|
|
|
7
7
|
import base64url from 'base64url'
|
|
8
8
|
import { css, html, LitElement, nothing } from 'lit'
|
|
9
9
|
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
10
|
+
import { startRegistration } from '@simplewebauthn/browser'
|
|
10
11
|
|
|
11
12
|
import { i18next, localize } from '@operato/i18n'
|
|
12
13
|
import { notify, openPopup } from '@operato/layout'
|
|
@@ -164,7 +165,7 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
|
|
|
164
165
|
|
|
165
166
|
${isAvailableWebauthn
|
|
166
167
|
? html`
|
|
167
|
-
<md-text-button @click=${() => this.
|
|
168
|
+
<md-text-button @click=${() => this.registerUser()}
|
|
168
169
|
>${i18next.t('button.security-key registration')}</md-text-button
|
|
169
170
|
>
|
|
170
171
|
`
|
|
@@ -244,96 +245,35 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
|
|
|
244
245
|
})
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
async
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
credentials: 'include'
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
if (!challenge.ok) {
|
|
259
|
-
notify({
|
|
260
|
-
level: 'error',
|
|
261
|
-
message: await challenge.text()
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const options = await challenge.json()
|
|
268
|
-
|
|
269
|
-
const credential = (await navigator.credentials.create({
|
|
270
|
-
publicKey: {
|
|
271
|
-
rp: {
|
|
272
|
-
name: this.applicationMeta.title
|
|
273
|
-
},
|
|
274
|
-
user: {
|
|
275
|
-
id: Uint8Array.from(base64url.toBuffer(options.user.id)).buffer,
|
|
276
|
-
name: options.user.name,
|
|
277
|
-
displayName: options.user.displayName
|
|
248
|
+
async registerUser() {
|
|
249
|
+
try {
|
|
250
|
+
const options = await fetch('/auth/register-webauthn/challenge').then(res => res.json())
|
|
251
|
+
const attResp = await startRegistration(options)
|
|
252
|
+
const verification = await fetch('/auth/verify-registration', {
|
|
253
|
+
method: 'POST',
|
|
254
|
+
headers: {
|
|
255
|
+
'Content-Type': 'application/json'
|
|
278
256
|
},
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
257
|
+
body: JSON.stringify(attResp)
|
|
258
|
+
}).then(res => res.json())
|
|
259
|
+
|
|
260
|
+
if (verification.verified) {
|
|
261
|
+
notify({
|
|
262
|
+
level: 'info',
|
|
263
|
+
message: i18next.t('text.user credential registered successfully')
|
|
264
|
+
})
|
|
265
|
+
} else {
|
|
266
|
+
console.error(await verification.text())
|
|
267
|
+
|
|
268
|
+
notify({
|
|
269
|
+
level: 'error',
|
|
270
|
+
message: i18next.t('error.user credential registeration not allowed')
|
|
271
|
+
})
|
|
293
272
|
}
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
if (!credential) {
|
|
273
|
+
} catch (error) {
|
|
297
274
|
notify({
|
|
298
275
|
level: 'error',
|
|
299
|
-
message: '
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const response = credential.response as AuthenticatorAttestationResponse
|
|
306
|
-
|
|
307
|
-
var body = {
|
|
308
|
-
response: {
|
|
309
|
-
clientDataJSON: Buffer.from(response.clientDataJSON).toString('base64'),
|
|
310
|
-
attestationObject: Buffer.from(response.attestationObject).toString('base64')
|
|
311
|
-
} as any
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (response.getTransports) {
|
|
315
|
-
body.response.transports = response.getTransports()
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const signinResponse = await fetch('/auth/signin-webauthn', {
|
|
319
|
-
method: 'POST',
|
|
320
|
-
headers: {
|
|
321
|
-
'Content-Type': 'application/json',
|
|
322
|
-
Accept: 'application/json'
|
|
323
|
-
},
|
|
324
|
-
body: JSON.stringify(body),
|
|
325
|
-
credentials: 'include'
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
if (!signinResponse.ok) {
|
|
329
|
-
notify({
|
|
330
|
-
level: 'error',
|
|
331
|
-
message: await signinResponse.text()
|
|
332
|
-
})
|
|
333
|
-
} else {
|
|
334
|
-
notify({
|
|
335
|
-
level: 'info',
|
|
336
|
-
message: i18next.t('text.user credential registered successfully')
|
|
276
|
+
message: i18next.t('error.user credential registeration failed')
|
|
337
277
|
})
|
|
338
278
|
}
|
|
339
279
|
}
|
|
@@ -20,7 +20,8 @@ export class AuthActivate extends localize(i18next)(LitElement) {
|
|
|
20
20
|
width: 100vw;
|
|
21
21
|
height: 100vh;
|
|
22
22
|
height: 100dvh;
|
|
23
|
-
background-color: var(--
|
|
23
|
+
background-color: var(--md-sys-color-primary);
|
|
24
|
+
color: var(--md-sys-color-on-primary);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
.wrap {
|
|
@@ -21,12 +21,14 @@ export class AuthCheckIn extends localize(i18next)(LitElement) {
|
|
|
21
21
|
display: flex;
|
|
22
22
|
flex-direction: column;
|
|
23
23
|
margin: auto;
|
|
24
|
-
background-color: var(--
|
|
24
|
+
background-color: var(--md-sys-color-primary);
|
|
25
|
+
color: var(--md-sys-color-on-primary);
|
|
25
26
|
height: 100vh;
|
|
26
27
|
height: 100dvh;
|
|
27
28
|
}
|
|
28
29
|
.header {
|
|
29
30
|
background-color: var(--md-sys-color-primary);
|
|
31
|
+
color: var(--md-sys-color-on-primary);
|
|
30
32
|
height: var(--checkin-header-height);
|
|
31
33
|
}
|
|
32
34
|
.content {
|
|
@@ -38,6 +40,7 @@ export class AuthCheckIn extends localize(i18next)(LitElement) {
|
|
|
38
40
|
margin: var(--margin-wide) 0;
|
|
39
41
|
padding: 0;
|
|
40
42
|
background-color: var(--md-sys-color-surface);
|
|
43
|
+
color: var(--md-sys-color-on-surface);
|
|
41
44
|
border-radius: var(--border-radius);
|
|
42
45
|
border: var(--border-dim-color);
|
|
43
46
|
list-style: none;
|
|
@@ -47,13 +50,13 @@ export class AuthCheckIn extends localize(i18next)(LitElement) {
|
|
|
47
50
|
margin-bottom: -1px;
|
|
48
51
|
padding: var(--padding-default) var(--padding-wide);
|
|
49
52
|
font-size: 18px;
|
|
50
|
-
color: var(--md-sys-color-secondary);
|
|
51
53
|
text-align: left;
|
|
52
54
|
|
|
53
55
|
cursor: pointer;
|
|
54
56
|
}
|
|
55
57
|
li:hover {
|
|
56
58
|
background-color: var(--md-sys-color-primary-container);
|
|
59
|
+
color: var(--md-sys-color-on-primary-container);
|
|
57
60
|
}
|
|
58
61
|
li span {
|
|
59
62
|
display: block;
|
|
@@ -18,7 +18,8 @@ export class AuthResult extends localize(i18next)(LitElement) {
|
|
|
18
18
|
width: 100vw;
|
|
19
19
|
height: 100vh;
|
|
20
20
|
height: 100dvh;
|
|
21
|
-
background-color: var(--
|
|
21
|
+
background-color: var(--md-sys-color-primary);
|
|
22
|
+
color: var(--md-sys-color-on-primary);
|
|
22
23
|
}
|
|
23
24
|
.wrap {
|
|
24
25
|
display: block;
|
|
@@ -2,7 +2,8 @@ import { css } from 'lit';
|
|
|
2
2
|
export const AUTH_STYLE_SIGN = css `
|
|
3
3
|
:host {
|
|
4
4
|
display: flex;
|
|
5
|
-
background-color: var(--
|
|
5
|
+
background-color: var(--md-sys-color-primary);
|
|
6
|
+
color: var(--md-sys-color-on-primary);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
:host *:focus {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-style-sign.js","sourceRoot":"","sources":["../client/auth-style-sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"auth-style-sign.js","sourceRoot":"","sources":["../client/auth-style-sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6LjC,CAAA","sourcesContent":["import { css } from 'lit'\n\nexport const AUTH_STYLE_SIGN = css`\n :host {\n display: flex;\n background-color: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n :host *:focus {\n outline: none;\n }\n\n :host * {\n box-sizing: border-box;\n }\n\n .wrap {\n display: block;\n width: 450px;\n min-width: 350px;\n margin: 0 auto;\n padding-bottom: 100px;\n text-align: center;\n }\n\n .auth-brand {\n color: var(--md-sys-color-on-primary);\n }\n\n .auth-brand img {\n margin: 15% auto 5px auto;\n width: 100px;\n border: 3px solid var(--md-sys-color-on-primary);\n border-radius: 25px;\n box-shadow: var(--box-shadow);\n }\n .name {\n display: block;\n font: var(--auth-brand-name);\n text-shadow: var(--auth-brand-name-shadow);\n }\n .auth-brand .welcome-msg {\n font: var(--auth-brand-welcome-msg);\n }\n .auth-form {\n display: grid;\n grid-gap: var(--margin-default);\n grid-template-columns: 1fr 1fr;\n }\n\n form {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: var(--margin-default);\n align-items: center;\n }\n\n h3 {\n grid-column: 1 / -1;\n margin: 50px 0 0 0;\n font: var(--auth-title-font);\n color: var(--auth-title-color, var(--md-sys-color-on-primary));\n text-transform: uppercase;\n }\n\n .field {\n grid-column: 1 / -1;\n text-align: left;\n }\n\n .submit-buttons-container {\n grid-column: 1 / -1;\n text-align: center;\n\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .fingerprint {\n color: var(--md-sys-color-on-primary);\n border: 1.5px solid var(--md-sys-color-on-primary);\n border-radius: 20%;\n width: 36px;\n height: 36px;\n }\n\n .field md-filled-text-field {\n grid-column: 1 / -1;\n width: 100%;\n }\n\n md-text-button,\n md-elevated-button {\n grid-column: 1 / -1;\n flex: 1;\n }\n\n .wrap .link {\n text-decoration: none;\n justify-self: flex-start;\n }\n\n .wrap .link md-text-button {\n --md-text-button-label-text-color: var(--md-sys-color-on-primary);\n --md-text-button-focus-label-text-color: var(--md-sys-color-on-primary);\n --md-text-button-hover-label-text-color: var(--md-sys-color-on-primary);\n }\n\n .wrap .link md-icon {\n color: var(--md-sys-color-on-primary);\n }\n\n #locale-area {\n display: flex;\n grid-column: 1 / -1;\n padding: 0 var(--padding-default);\n }\n\n #locale-area > label {\n display: flex;\n align-items: center;\n color: var(--md-sys-color-on-primary);\n --md-icon-size: 16px;\n }\n\n #locale-selector {\n font-size: 16px;\n width: 100%;\n }\n\n #locale-selector {\n --i18n-selector-field-border: none;\n --i18n-selector-field-background-color: none;\n --i18n-selector-field-font-size: 14px;\n --i18n-selector-field-color: var(--md-sys-color-on-primary);\n }\n\n .lottie-container {\n width: 100%;\n height: 300px;\n position: absolute;\n left: 0;\n bottom: 0;\n pointer-events: none;\n }\n .lottie-container lottie-player {\n position: absolute;\n bottom: -6%;\n width: 100%;\n height: auto;\n }\n\n @media (max-width: 450px) {\n .wrap {\n width: 85%;\n min-width: 320px;\n }\n .auth-form {\n grid-template-columns: 1fr;\n }\n .auth-brand img {\n margin: 12% auto 5px auto;\n width: 75px;\n }\n h3 {\n margin: 15px 0 0 0;\n }\n .lottie-container {\n overflow: hidden;\n height: 200px;\n pointer-events: none;\n }\n .lottie-container lottie-player {\n width: 1200px;\n left: -30%;\n }\n }\n\n @media screen and (min-width: 1400px) {\n .wrap {\n padding-bottom: 150px;\n }\n }\n @media screen and (min-width: 2500px) {\n .wrap {\n padding-bottom: 280px;\n }\n }\n`\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { html, nothing } from 'lit';
|
|
2
|
-
import base64url from 'base64url';
|
|
3
1
|
import '@operato/i18n/ox-i18n.js';
|
|
2
|
+
import { html, nothing } from 'lit';
|
|
3
|
+
import { startAuthentication } from '@simplewebauthn/browser';
|
|
4
4
|
import { i18next } from '@operato/i18n';
|
|
5
5
|
import { notify } from '@operato/layout';
|
|
6
6
|
import { AbstractAuthPage } from './abstract-auth-page.js';
|
|
@@ -66,75 +66,41 @@ export class AbstractSign extends AbstractAuthPage {
|
|
|
66
66
|
<ox-i18n msgid="field.${this.pageName}"> </ox-i18n>
|
|
67
67
|
</md-elevated-button>
|
|
68
68
|
${isAvailableWebauthn
|
|
69
|
-
? html ` <md-icon class="fingerprint" raised @click=${e => this.
|
|
69
|
+
? html ` <md-icon class="fingerprint" raised @click=${e => this.authenticateUser()}> fingerprint </md-icon>`
|
|
70
70
|
: nothing}
|
|
71
71
|
</div>
|
|
72
72
|
`;
|
|
73
73
|
}
|
|
74
|
-
async
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const options = await response.json();
|
|
91
|
-
const credential = (await navigator.credentials.get({
|
|
92
|
-
publicKey: {
|
|
93
|
-
challenge: Buffer.from(options.challenge, 'base64')
|
|
74
|
+
async authenticateUser() {
|
|
75
|
+
try {
|
|
76
|
+
const options = await fetch('/auth/signin-webauthn/challenge').then(res => res.json());
|
|
77
|
+
const assertionResp = await startAuthentication(options);
|
|
78
|
+
const verification = await fetch('/auth/signin-webauthn', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify(assertionResp)
|
|
84
|
+
}).then(res => res.json());
|
|
85
|
+
if (verification.verified) {
|
|
86
|
+
const { redirectURL } = verification;
|
|
87
|
+
if (redirectURL) {
|
|
88
|
+
window.location.href = redirectURL;
|
|
89
|
+
}
|
|
94
90
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const assertionResponse = {
|
|
104
|
-
id: credential.id,
|
|
105
|
-
response: {
|
|
106
|
-
clientDataJSON: base64url.encode(Buffer.from(credential.response.clientDataJSON)),
|
|
107
|
-
authenticatorData: base64url.encode(credential.response.authenticatorData),
|
|
108
|
-
signature: base64url.encode(credential.response.signature),
|
|
109
|
-
userHandle: credential.response.userHandle
|
|
110
|
-
? base64url.encode(credential.response.userHandle)
|
|
111
|
-
: null
|
|
91
|
+
else {
|
|
92
|
+
notify({
|
|
93
|
+
level: 'error',
|
|
94
|
+
message: verification.message
|
|
95
|
+
});
|
|
112
96
|
}
|
|
113
|
-
};
|
|
114
|
-
if (credential.authenticatorAttachment) {
|
|
115
|
-
assertionResponse.authenticatorAttachment = credential.authenticatorAttachment;
|
|
116
97
|
}
|
|
117
|
-
|
|
118
|
-
method: 'POST',
|
|
119
|
-
headers: {
|
|
120
|
-
'Content-Type': 'application/json',
|
|
121
|
-
Accept: 'application/json'
|
|
122
|
-
},
|
|
123
|
-
body: JSON.stringify(assertionResponse),
|
|
124
|
-
credentials: 'include'
|
|
125
|
-
});
|
|
126
|
-
if (!signinResponse.ok) {
|
|
98
|
+
catch (error) {
|
|
127
99
|
notify({
|
|
128
100
|
level: 'error',
|
|
129
|
-
message:
|
|
101
|
+
message: i18next.t('error.authn verification failed')
|
|
130
102
|
});
|
|
131
103
|
}
|
|
132
|
-
else {
|
|
133
|
-
const { redirectURL } = await signinResponse.json();
|
|
134
|
-
if (redirectURL) {
|
|
135
|
-
window.location.href = redirectURL;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
104
|
}
|
|
139
105
|
}
|
|
140
106
|
//# sourceMappingURL=abstract-sign.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-sign.js","sourceRoot":"","sources":["../../client/components/abstract-sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,
|
|
1
|
+
{"version":3,"file":"abstract-sign.js","sourceRoot":"","sources":["../../client/components/abstract-sign.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/i18n/ox-i18n.js'\n\nimport { html, nothing } from 'lit'\nimport { startAuthentication } from '@simplewebauthn/browser'\n\nimport { i18next } from '@operato/i18n'\nimport { notify } from '@operato/layout'\n\nimport { AbstractAuthPage } from './abstract-auth-page.js'\n\nconst isAvailableWebauthn = 'PublicKeyCredential' in window\n\ninterface AssertionResponse {\n id: string\n rawId?: number[]\n response: {\n authenticatorData: string\n clientDataJSON: string\n signature: string\n userHandle: string | null\n }\n type: PublicKeyCredentialType\n authenticatorAttachment?: AuthenticatorAttachment\n}\n\nexport abstract class AbstractSign extends AbstractAuthPage {\n async submit() {\n this.formEl.submit()\n }\n\n updated(changed) {\n super.updated(changed)\n\n if (changed.has('message')) {\n if (!this.message) {\n this.hideSnackbar()\n } else {\n this.showSnackbar({\n level: 'error',\n timer: -1\n })\n }\n }\n }\n\n get formfields() {\n const email = this.data?.email || ''\n\n return html`\n <input id=\"redirectTo\" type=\"hidden\" name=\"redirectTo\" .value=${this.redirectTo || '/'} />\n\n <div class=\"field\">\n <md-filled-text-field\n name=\"email\"\n type=\"email\"\n label=${String(i18next.t('field.email'))}\n required\n .value=${email}\n autocomplete=\"username\"\n autocapitalize=\"off\"\n @input=${(e: Event) => {\n const target = e.target as HTMLInputElement\n if (target.validity.typeMismatch) {\n target.setCustomValidity(i18next.t('text.invalid-email'))\n } else {\n target.setCustomValidity('')\n }\n }}\n ><md-icon slot=\"leading-icon\">mail</md-icon></md-filled-text-field\n >\n </div>\n <div class=\"field\">\n <md-filled-text-field\n name=\"password\"\n type=\"password\"\n label=${String(i18next.t('field.password'))}\n autocomplete=\"current-password\"\n required\n ><md-icon slot=\"leading-icon\">vpn_key</md-icon></md-filled-text-field\n >\n </div>\n\n <div class=\"submit-buttons-container\">\n <md-elevated-button class=\"submit-button\" type=\"submit\" raised @click=${e => this._onSubmit(e)}>\n <ox-i18n msgid=\"field.${this.pageName}\"> </ox-i18n>\n </md-elevated-button>\n ${isAvailableWebauthn\n ? html` <md-icon class=\"fingerprint\" raised @click=${e => this.authenticateUser()}> fingerprint </md-icon>`\n : nothing}\n </div>\n `\n }\n\n async authenticateUser() {\n try {\n const options = await fetch('/auth/signin-webauthn/challenge').then(res => res.json())\n const assertionResp = await startAuthentication(options)\n const verification = await fetch('/auth/signin-webauthn', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(assertionResp)\n }).then(res => res.json())\n\n if (verification.verified) {\n const { redirectURL } = verification\n \n if (redirectURL) {\n window.location.href = redirectURL\n }\n } else {\n notify({\n level: 'error',\n message: verification.message\n })\n }\n \n } catch (error) {\n notify({\n level: 'error',\n message: i18next.t('error.authn verification failed')\n })\n }\n }\n}\n"]}
|
|
@@ -23,7 +23,7 @@ export declare class ProfileComponent extends ProfileComponent_base {
|
|
|
23
23
|
onLocaleChanged(value: any): Promise<void>;
|
|
24
24
|
openLoginHistory(): void;
|
|
25
25
|
deleteUser(): void;
|
|
26
|
-
|
|
26
|
+
registerUser(): Promise<void>;
|
|
27
27
|
get applicationMeta(): {
|
|
28
28
|
icon: string;
|
|
29
29
|
title: string;
|