mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 19:38:23 +00:00 
			
		
		
		
	Add Passkey login support (#31504)
closes #22015 After adding a passkey, you can now simply login with it directly by clicking `Sign in with a passkey`.  Note for testing. You need to run gitea using `https` to get the full passkeys experience. --------- Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		| @@ -5,25 +5,88 @@ import {GET, POST} from '../modules/fetch.js'; | ||||
| const {appSubUrl} = window.config; | ||||
|  | ||||
| export async function initUserAuthWebAuthn() { | ||||
|   const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); | ||||
|   if (!elPrompt) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!detectWebAuthnSupport()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const res = await GET(`${appSubUrl}/user/webauthn/assertion`); | ||||
|   if (res.status !== 200) { | ||||
|   const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); | ||||
|   if (elSignInPasskeyBtn) { | ||||
|     elSignInPasskeyBtn.addEventListener('click', loginPasskey); | ||||
|   } | ||||
|  | ||||
|   const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); | ||||
|   if (elPrompt) { | ||||
|     login2FA(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function loginPasskey() { | ||||
|   const res = await GET(`${appSubUrl}/user/webauthn/passkey/assertion`); | ||||
|   if (!res.ok) { | ||||
|     webAuthnError('unknown'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const options = await res.json(); | ||||
|   options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge); | ||||
|   for (const cred of options.publicKey.allowCredentials) { | ||||
|   for (const cred of options.publicKey.allowCredentials ?? []) { | ||||
|     cred.id = decodeURLEncodedBase64(cred.id); | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const credential = await navigator.credentials.get({ | ||||
|       publicKey: options.publicKey, | ||||
|     }); | ||||
|  | ||||
|     // Move data into Arrays in case it is super long | ||||
|     const authData = new Uint8Array(credential.response.authenticatorData); | ||||
|     const clientDataJSON = new Uint8Array(credential.response.clientDataJSON); | ||||
|     const rawId = new Uint8Array(credential.rawId); | ||||
|     const sig = new Uint8Array(credential.response.signature); | ||||
|     const userHandle = new Uint8Array(credential.response.userHandle); | ||||
|  | ||||
|     const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, { | ||||
|       data: { | ||||
|         id: credential.id, | ||||
|         rawId: encodeURLEncodedBase64(rawId), | ||||
|         type: credential.type, | ||||
|         clientExtensionResults: credential.getClientExtensionResults(), | ||||
|         response: { | ||||
|           authenticatorData: encodeURLEncodedBase64(authData), | ||||
|           clientDataJSON: encodeURLEncodedBase64(clientDataJSON), | ||||
|           signature: encodeURLEncodedBase64(sig), | ||||
|           userHandle: encodeURLEncodedBase64(userHandle), | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
|     if (res.status === 500) { | ||||
|       webAuthnError('unknown'); | ||||
|       return; | ||||
|     } else if (!res.ok) { | ||||
|       webAuthnError('unable-to-process'); | ||||
|       return; | ||||
|     } | ||||
|     const reply = await res.json(); | ||||
|  | ||||
|     window.location.href = reply?.redirect ?? `${appSubUrl}/`; | ||||
|   } catch (err) { | ||||
|     webAuthnError('general', err.message); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function login2FA() { | ||||
|   const res = await GET(`${appSubUrl}/user/webauthn/assertion`); | ||||
|   if (!res.ok) { | ||||
|     webAuthnError('unknown'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const options = await res.json(); | ||||
|   options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge); | ||||
|   for (const cred of options.publicKey.allowCredentials ?? []) { | ||||
|     cred.id = decodeURLEncodedBase64(cred.id); | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const credential = await navigator.credentials.get({ | ||||
|       publicKey: options.publicKey, | ||||
| @@ -71,7 +134,7 @@ async function verifyAssertion(assertedCredential) { | ||||
|   if (res.status === 500) { | ||||
|     webAuthnError('unknown'); | ||||
|     return; | ||||
|   } else if (res.status !== 200) { | ||||
|   } else if (!res.ok) { | ||||
|     webAuthnError('unable-to-process'); | ||||
|     return; | ||||
|   } | ||||
| @@ -167,7 +230,7 @@ async function webAuthnRegisterRequest() { | ||||
|   if (res.status === 409) { | ||||
|     webAuthnError('duplicated'); | ||||
|     return; | ||||
|   } else if (res.status !== 200) { | ||||
|   } else if (!res.ok) { | ||||
|     webAuthnError('unknown'); | ||||
|     return; | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user