|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html lang="en"> |
3 | 3 | <head> |
4 | | - <meta charset="utf-8"> |
5 | | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | | - <meta http-equiv="x-ua-compatible" content="ie=edge"> |
| 4 | + <meta charset="utf-8" /> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | + <meta http-equiv="x-ua-compatible" content="ie=edge" /> |
7 | 7 | <title>Passkeys Demo</title> |
8 | 8 |
|
9 | | - <link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico"> |
10 | | - <link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css"> |
11 | | - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@19.5.3/build/css/intlTelInput.css"> |
12 | | - <script src="https://cdn.jsdelivr.net/npm/intl-tel-input@19.5.3/build/js/intlTelInput.min.js"></script> |
| 9 | + <link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico" /> |
| 10 | + <link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css" /> |
| 11 | + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@25.3.1/build/css/intlTelInput.css" /> |
| 12 | + <script src="https://cdn.jsdelivr.net/npm/intl-tel-input@25.3.1/build/js/intlTelInput.min.js"></script> |
13 | 13 | <script src=" https://cdn.jsdelivr.net/npm/@github/[email protected]/dist/browser-global/webauthn-json.browser-global.min.js" ></script> |
14 | 14 | <style> |
15 | 15 | body { |
|
22 | 22 | background-color: rgb(237, 242, 247); |
23 | 23 | } |
24 | 24 |
|
25 | | - #container, #modal, #app { |
| 25 | + #container, |
| 26 | + #modal, |
| 27 | + #app { |
26 | 28 | max-width: 1280px; |
27 | 29 | margin: 0 auto; |
28 | 30 | padding: 4rem; |
29 | 31 | text-align: center; |
30 | 32 | border-radius: 10px; |
31 | | - background-color: #FFFFFF; |
| 33 | + background-color: #ffffff; |
32 | 34 | box-shadow: 100px 100px 69px -29px rgba(0, 0, 0, 0.07); |
33 | 35 | } |
34 | 36 |
|
|
46 | 48 | border-radius: 4px; |
47 | 49 | padding: 0.8rem; |
48 | 50 | font-size: 16px; |
| 51 | + padding-left: 36px !important; |
49 | 52 | } |
50 | 53 |
|
51 | 54 | .invisible { |
|
54 | 57 |
|
55 | 58 | .iti { |
56 | 59 | display: flex; |
57 | | - gap: 10px |
| 60 | + gap: 10px; |
58 | 61 | } |
59 | 62 |
|
60 | 63 | .iti__arrow { |
|
69 | 72 | } |
70 | 73 |
|
71 | 74 | .input_container .iti__selected-flag { |
72 | | - background-color: #FFFFFF; |
| 75 | + background-color: #ffffff; |
73 | 76 | } |
74 | 77 |
|
75 | 78 | .input_container { |
|
91 | 94 |
|
92 | 95 | .input_component > span { |
93 | 96 | font-size: 14px; |
94 | | - color: #D04848; |
| 97 | + color: #d04848; |
95 | 98 | } |
96 | 99 |
|
97 | 100 | .hide { |
98 | 101 | visibility: hidden; |
99 | 102 | } |
100 | 103 |
|
101 | | - |
102 | 104 | .btn { |
103 | 105 | padding: 15px 0; |
104 | 106 | border-radius: 50px; |
|
108 | 110 |
|
109 | 111 | .continue_btn { |
110 | 112 | border-width: 0; |
111 | | - color: #FFFFFF; |
| 113 | + color: #ffffff; |
112 | 114 | background-color: rgb(205, 210, 216); |
113 | 115 | } |
114 | 116 |
|
115 | 117 | .enable { |
116 | | - background-color:rgb(2, 99, 224); |
| 118 | + background-color: rgb(2, 99, 224); |
117 | 119 | } |
118 | 120 |
|
119 | 121 | .skip_btn { |
120 | 122 | margin: 20px 0 0 0; |
121 | 123 | } |
122 | 124 |
|
123 | | - a, button { |
| 125 | + a, |
| 126 | + button { |
124 | 127 | cursor: pointer; |
125 | 128 | } |
126 | 129 |
|
127 | 130 | .separator { |
128 | | - color: #7F8487; |
| 131 | + color: #7f8487; |
129 | 132 | font-size: 10px; |
130 | 133 | } |
131 | 134 |
|
132 | 135 | .passkey_btn { |
133 | 136 | border-width: 1px; |
134 | 137 | border-color: rgb(2, 99, 224); |
135 | 138 | color: rgb(2, 99, 224); |
136 | | - background-color: #FFFFFF; |
| 139 | + background-color: #ffffff; |
137 | 140 | border-style: solid; |
138 | 141 | } |
139 | 142 | </style> |
|
143 | 146 | <h1 class="title">Sign up or sign in</h1> |
144 | 147 | <div class="input_container"> |
145 | 148 | <div class="input_component"> |
146 | | - <input type="tel" name="username_input" id="usr_input" oninput="checkAvalibility()"> |
| 149 | + <input |
| 150 | + type="tel" |
| 151 | + name="username_input" |
| 152 | + id="usr_input" |
| 153 | + oninput="checkAvalibility()" |
| 154 | + /> |
147 | 155 | </div> |
148 | | - <button type="button" class="btn continue_btn" id="continue" onclick="login()" disabled>Continue</button> |
| 156 | + <button |
| 157 | + type="button" |
| 158 | + class="btn continue_btn" |
| 159 | + id="continue" |
| 160 | + onclick="login()" |
| 161 | + disabled |
| 162 | + > |
| 163 | + Continue |
| 164 | + </button> |
149 | 165 | <span class="separator">― or ―</span> |
150 | | - <button type="button" class="btn passkey_btn" onclick="signIn()">Sign in with passkey</button> |
| 166 | + <button type="button" class="btn passkey_btn" onclick="signIn()"> |
| 167 | + Sign in with passkey |
| 168 | + </button> |
151 | 169 | </div> |
152 | 170 | </div> |
153 | 171 | <div id="modal" class="invisible"> |
154 | | - <h1 class="title">Sign-in with your face,<br> fingerprint or PIN</h1> |
155 | | - <p>Harness your device capabilities for a fast<br> passkey login with maximun security.</p> |
| 172 | + <h1 class="title"> |
| 173 | + Sign-in with your face,<br /> |
| 174 | + fingerprint or PIN |
| 175 | + </h1> |
| 176 | + <p> |
| 177 | + Harness your device capabilities for a fast<br /> |
| 178 | + passkey login with maximun security. |
| 179 | + </p> |
156 | 180 | <a>Learn more →</a> |
157 | 181 | <div class="input_container modal_input"> |
158 | | - <button class="btn continue_btn enable" onclick="signUp()">Continue</button> |
| 182 | + <button class="btn continue_btn enable" onclick="signUp()"> |
| 183 | + Continue |
| 184 | + </button> |
159 | 185 | <a class="skip_btn">Skip</a> |
160 | 186 | </div> |
161 | 187 | </div> |
162 | 188 | <div id="app" class="invisible"> |
163 | 189 | <h1 class="title" id="welcome"></h1> |
164 | 190 | <div class="input_container"> |
165 | | - <button class="btn continue_btn enable" onclick="logOut()">Log out</button> |
| 191 | + <button class="btn continue_btn enable" onclick="logOut()"> |
| 192 | + Log out |
| 193 | + </button> |
166 | 194 | </div> |
167 | 195 | </div> |
168 | 196 | </body> |
169 | 197 | <script> |
170 | | - |
171 | 198 | let sessionUsername = sessionStorage.getItem("session"); |
172 | 199 |
|
173 | 200 | const loadApp = (username) => { |
174 | | - document.getElementById("welcome").innerHTML = `Welcome ${username}` |
| 201 | + document.getElementById("welcome").innerHTML = `Welcome ${username}`; |
175 | 202 | document.getElementById("modal").classList.add("invisible"); |
176 | 203 | document.getElementById("container").classList.add("invisible"); |
177 | 204 | document.getElementById("app").classList.remove("invisible"); |
178 | | - } |
| 205 | + }; |
179 | 206 |
|
180 | 207 | if (sessionUsername) { |
181 | | - loadApp(sessionUsername) |
| 208 | + loadApp(sessionUsername); |
182 | 209 | } |
183 | | - |
| 210 | + |
184 | 211 | const usernameElement = document.getElementById("usr_input"); |
185 | 212 | const errorElement = document.getElementById("error"); |
186 | 213 | const continueButton = document.getElementById("continue"); |
187 | | - window.intlTelInput(usernameElement, { |
| 214 | + const iti = window.intlTelInput(usernameElement, { |
188 | 215 | initialCountry: "us", |
189 | 216 | showSelectedDialCode: true, |
190 | | - utilsScript: "https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js", |
| 217 | + loadUtils: () => |
| 218 | + import( |
| 219 | + "https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js" |
| 220 | + ), |
191 | 221 | }); |
192 | 222 |
|
193 | 223 | const checkAvalibility = () => { |
194 | | - const username = usernameElement.value |
195 | | - if(username) { |
| 224 | + const username = usernameElement.value; |
| 225 | + if (username) { |
196 | 226 | continueButton.classList.add("enable"); |
197 | 227 | continueButton.disabled = false; |
198 | 228 | } else { |
199 | 229 | continueButton.classList.remove("enable"); |
200 | 230 | continueButton.disabled = true; |
201 | 231 | } |
202 | | - } |
| 232 | + }; |
203 | 233 |
|
204 | 234 | const login = () => { |
205 | 235 | const authenticationCard = document.getElementById("container"); |
206 | 236 | const passkeyCard = document.getElementById("modal"); |
207 | 237 | authenticationCard.classList.add("invisible"); |
208 | 238 | passkeyCard.classList.remove("invisible"); |
209 | | - } |
| 239 | + }; |
210 | 240 |
|
211 | 241 | const signIn = async () => { |
212 | 242 | try { |
213 | 243 | const response = await fetch(`./authentication/start`); |
214 | 244 | const responseJSON = await response.json(); |
215 | 245 |
|
216 | | - window.webauthnJSON.get(responseJSON) |
| 246 | + window.webauthnJSON |
| 247 | + .get(responseJSON) |
217 | 248 | .then(async (publicKeyCredential) => { |
218 | | - |
219 | | - const authentication = await fetch('./authentication/verification', { |
220 | | - method: "POST", |
221 | | - headers: { |
222 | | - "Content-Type": "application/json" |
223 | | - }, |
224 | | - body: JSON.stringify(publicKeyCredential) |
225 | | - }); |
| 249 | + const authentication = await fetch( |
| 250 | + "./authentication/verification", |
| 251 | + { |
| 252 | + method: "POST", |
| 253 | + headers: { |
| 254 | + "Content-Type": "application/json", |
| 255 | + }, |
| 256 | + body: JSON.stringify(publicKeyCredential), |
| 257 | + } |
| 258 | + ); |
226 | 259 |
|
227 | 260 | const { status, identity } = await authentication.json(); |
228 | | - if(status === "approved") { |
229 | | - sessionStorage.setItem('session', identity); |
| 261 | + if (status === "approved") { |
| 262 | + sessionStorage.setItem("session", identity); |
230 | 263 | loadApp(identity); |
231 | 264 | } else { |
232 | 265 | console.log(status); |
233 | 266 | } |
234 | 267 | }) |
235 | 268 | .catch((err) => { |
236 | | - if(err) { |
237 | | - console.error("Something goes wrong or maybe you dont have a passkey for this application yet"); |
| 269 | + if (err) { |
| 270 | + console.error( |
| 271 | + "Something went wrong - try registering a new passkey for this application." |
| 272 | + ); |
238 | 273 | } |
239 | 274 | }); |
240 | 275 | } catch (error) { |
241 | 276 | console.log(err); |
242 | 277 | } |
243 | | - } |
244 | | - |
| 278 | + }; |
| 279 | + |
245 | 280 | const signUp = async () => { |
246 | | - const username = usernameElement.value |
| 281 | + const username = iti.getNumber( |
| 282 | + window.intlTelInput.utils.numberFormat.E164 |
| 283 | + ); |
247 | 284 |
|
248 | 285 | try { |
249 | 286 | const response = await fetch(`./registration/start`, { |
250 | 287 | method: "POST", |
251 | 288 | headers: { |
252 | | - "Content-Type": "application/json" |
| 289 | + "Content-Type": "application/json", |
253 | 290 | }, |
254 | | - body: JSON.stringify({ username }) |
| 291 | + body: JSON.stringify({ username }), |
255 | 292 | }); |
256 | 293 |
|
257 | 294 | const responseJSON = await response.json(); |
258 | | - |
259 | | - let credential = await window.webauthnJSON.create({publicKey: responseJSON}); |
260 | 295 |
|
261 | | - const verificationResponse = await fetch(`./registration/verification`, { |
262 | | - method: "POST", |
263 | | - headers: { |
264 | | - "Content-Type": "application/json" |
265 | | - }, |
266 | | - body: JSON.stringify(credential) |
| 296 | + let credential = await window.webauthnJSON.create({ |
| 297 | + publicKey: responseJSON, |
267 | 298 | }); |
268 | 299 |
|
| 300 | + const verificationResponse = await fetch( |
| 301 | + `./registration/verification`, |
| 302 | + { |
| 303 | + method: "POST", |
| 304 | + headers: { |
| 305 | + "Content-Type": "application/json", |
| 306 | + }, |
| 307 | + body: JSON.stringify(credential), |
| 308 | + } |
| 309 | + ); |
| 310 | + |
269 | 311 | const { status } = await verificationResponse.json(); |
270 | | - if (status === 'verified') { |
271 | | - sessionStorage.setItem('session', username); |
| 312 | + if (status === "verified") { |
| 313 | + sessionStorage.setItem("session", username); |
272 | 314 | loadApp(username); |
273 | 315 | } |
274 | 316 | } catch (error) { |
275 | 317 | console.log(error); |
276 | 318 | } |
277 | | - } |
| 319 | + }; |
278 | 320 |
|
279 | 321 | const logOut = () => { |
280 | 322 | sessionStorage.removeItem("session"); |
281 | 323 | document.getElementById("app").classList.add("invisible"); |
282 | 324 | document.getElementById("container").classList.remove("invisible"); |
283 | | - } |
| 325 | + }; |
284 | 326 | </script> |
285 | 327 | </html> |
0 commit comments