]>
Commit | Line | Data |
---|---|---|
fbeac4ea WB |
1 | Ext.define('PBS.window.AddWebauthn', { |
2 | extend: 'Ext.window.Window', | |
3 | alias: 'widget.pbsAddWebauthn', | |
4 | mixins: ['Proxmox.Mixin.CBind'], | |
5 | ||
6 | onlineHelp: 'user_mgmt', | |
7 | ||
8 | modal: true, | |
9 | resizable: false, | |
10 | title: gettext('Add a Webauthn login token'), | |
11 | width: 512, | |
12 | ||
13 | user: undefined, | |
14 | fixedUser: false, | |
15 | ||
16 | initComponent: function() { | |
17 | let me = this; | |
18 | me.callParent(); | |
19 | Ext.GlobalEvents.fireEvent('proxmoxShowHelp', me.onlineHelp); | |
20 | }, | |
21 | ||
22 | viewModel: { | |
23 | data: { | |
24 | valid: false, | |
958055a7 | 25 | userid: null, |
3189d051 TL |
26 | }, |
27 | formulas: { | |
28 | passwordConfirmText: (get) => { | |
29 | let id = get('userid'); | |
30 | return Ext.String.format(gettext("Confirm password of '{0}'"), id); | |
31 | }, | |
fbeac4ea WB |
32 | }, |
33 | }, | |
34 | ||
35 | controller: { | |
36 | xclass: 'Ext.app.ViewController', | |
37 | ||
38 | control: { | |
39 | 'field': { | |
40 | validitychange: function(field, valid) { | |
41 | let me = this; | |
42 | let viewmodel = me.getViewModel(); | |
43 | let form = me.lookup('webauthn_form'); | |
44 | viewmodel.set('valid', form.isValid()); | |
45 | }, | |
46 | }, | |
47 | '#': { | |
48 | show: function() { | |
49 | let me = this; | |
50 | let view = me.getView(); | |
51 | ||
52 | if (Proxmox.UserName === 'root@pam') { | |
53 | view.lookup('password').setVisible(false); | |
54 | view.lookup('password').setDisabled(true); | |
55 | } | |
56 | }, | |
57 | }, | |
58 | }, | |
59 | ||
60 | registerWebauthn: async function() { | |
61 | let me = this; | |
62 | let values = me.lookup('webauthn_form').getValues(); | |
63 | values.type = "webauthn"; | |
64 | ||
65 | let userid = values.user; | |
66 | delete values.user; | |
67 | ||
758a827c WB |
68 | me.getView().mask(gettext('Please wait...'), 'x-mask-loading'); |
69 | ||
fbeac4ea WB |
70 | try { |
71 | let register_response = await PBS.Async.api2({ | |
72 | url: `/api2/extjs/access/tfa/${userid}`, | |
73 | method: 'POST', | |
74 | params: values, | |
75 | }); | |
76 | ||
77 | let data = register_response.result.data; | |
78 | if (!data.challenge) { | |
79 | throw "server did not respond with a challenge"; | |
80 | } | |
81 | ||
82 | let challenge_obj = JSON.parse(data.challenge); | |
83 | ||
84 | // Fix this up before passing it to the browser, but keep a copy of the original | |
85 | // string to pass in the response: | |
86 | let challenge_str = challenge_obj.publicKey.challenge; | |
87 | challenge_obj.publicKey.challenge = PBS.Utils.base64url_to_bytes(challenge_str); | |
88 | challenge_obj.publicKey.user.id = | |
89 | PBS.Utils.base64url_to_bytes(challenge_obj.publicKey.user.id); | |
90 | ||
91 | let msg = Ext.Msg.show({ | |
92 | title: `Webauthn: ${gettext('Setup')}`, | |
93 | message: gettext('Please press the button on your Webauthn Device'), | |
94 | buttons: [], | |
95 | }); | |
96 | ||
97 | let token_response = await navigator.credentials.create(challenge_obj); | |
98 | ||
99 | // We cannot pass ArrayBuffers to the API, so extract & convert the data. | |
100 | let response = { | |
101 | id: token_response.id, | |
102 | type: token_response.type, | |
103 | rawId: PBS.Utils.bytes_to_base64url(token_response.rawId), | |
104 | response: { | |
105 | attestationObject: PBS.Utils.bytes_to_base64url( | |
106 | token_response.response.attestationObject, | |
107 | ), | |
108 | clientDataJSON: PBS.Utils.bytes_to_base64url( | |
109 | token_response.response.clientDataJSON, | |
110 | ), | |
111 | }, | |
112 | }; | |
113 | ||
114 | msg.close(); | |
115 | ||
116 | let params = { | |
117 | type: "webauthn", | |
118 | challenge: challenge_str, | |
119 | value: JSON.stringify(response), | |
120 | }; | |
121 | ||
122 | if (values.password) { | |
123 | params.password = values.password; | |
124 | } | |
125 | ||
126 | await PBS.Async.api2({ | |
127 | url: `/api2/extjs/access/tfa/${userid}`, | |
128 | method: 'POST', | |
129 | params, | |
130 | }); | |
131 | } catch (error) { | |
132 | console.error(error); // for debugging if it's not displayable... | |
133 | Ext.Msg.alert(gettext('Error'), error); | |
134 | } | |
135 | ||
136 | me.getView().close(); | |
137 | }, | |
138 | }, | |
139 | ||
140 | items: [ | |
141 | { | |
142 | xtype: 'form', | |
143 | reference: 'webauthn_form', | |
144 | layout: 'anchor', | |
646221cc | 145 | border: false, |
fbeac4ea WB |
146 | bodyPadding: 10, |
147 | fieldDefaults: { | |
148 | anchor: '100%', | |
149 | }, | |
150 | items: [ | |
151 | { | |
152 | xtype: 'pmxDisplayEditField', | |
153 | name: 'user', | |
154 | cbind: { | |
155 | editable: (get) => !get('fixedUser'), | |
958055a7 | 156 | value: () => Proxmox.UserName, |
fbeac4ea WB |
157 | }, |
158 | fieldLabel: gettext('User'), | |
159 | editConfig: { | |
160 | xtype: 'pbsUserSelector', | |
161 | allowBlank: false, | |
162 | }, | |
163 | renderer: Ext.String.htmlEncode, | |
3189d051 TL |
164 | listeners: { |
165 | change: function(field, newValue, oldValue) { | |
166 | let vm = this.up('window').getViewModel(); | |
167 | vm.set('userid', newValue); | |
168 | }, | |
169 | }, | |
fbeac4ea WB |
170 | }, |
171 | { | |
172 | xtype: 'textfield', | |
173 | fieldLabel: gettext('Description'), | |
174 | allowBlank: false, | |
175 | name: 'description', | |
176 | maxLength: 256, | |
3189d051 | 177 | emptyText: gettext('For example: TFA device ID, required to identify multiple factors.'), |
fbeac4ea WB |
178 | }, |
179 | { | |
180 | xtype: 'textfield', | |
aab9a264 TL |
181 | name: 'password', |
182 | reference: 'password', | |
3189d051 | 183 | fieldLabel: gettext('Verify Password'), |
aab9a264 | 184 | inputType: 'password', |
fbeac4ea | 185 | minLength: 5, |
fbeac4ea WB |
186 | allowBlank: false, |
187 | validateBlank: true, | |
958055a7 TL |
188 | cbind: { |
189 | hidden: () => Proxmox.UserName === 'root@pam', | |
190 | disabled: () => Proxmox.UserName === 'root@pam', | |
191 | }, | |
3189d051 TL |
192 | bind: { |
193 | emptyText: '{passwordConfirmText}', | |
194 | }, | |
fbeac4ea WB |
195 | }, |
196 | ], | |
197 | }, | |
198 | ], | |
199 | ||
200 | buttons: [ | |
201 | { | |
202 | xtype: 'proxmoxHelpButton', | |
203 | }, | |
204 | '->', | |
205 | { | |
206 | xtype: 'button', | |
207 | text: gettext('Register Webauthn Device'), | |
208 | handler: 'registerWebauthn', | |
209 | bind: { | |
210 | disabled: '{!valid}', | |
211 | }, | |
212 | }, | |
213 | ], | |
214 | }); |