mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Fix bugs with WebAuthn preventing sign in and registration. (#22651)
This PR fixes two bugs with Webauthn support: * There was a longstanding bug within webauthn due to the backend using URLEncodedBase64 but the javascript using decoding using plain base64. This causes intermittent issues with users reporting decoding errors. * Following the recent upgrade to webauthn there was a change in the way the library expects RPOrigins to be configured. This leads to the Relying Party Origin not being configured and prevents registration. Fix #22507 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -28,7 +28,7 @@ func Init() {
 | 
				
			|||||||
		Config: &webauthn.Config{
 | 
							Config: &webauthn.Config{
 | 
				
			||||||
			RPDisplayName: setting.AppName,
 | 
								RPDisplayName: setting.AppName,
 | 
				
			||||||
			RPID:          setting.Domain,
 | 
								RPID:          setting.Domain,
 | 
				
			||||||
			RPOrigin:      appURL,
 | 
								RPOrigins:     []string{appURL},
 | 
				
			||||||
			AuthenticatorSelection: protocol.AuthenticatorSelection{
 | 
								AuthenticatorSelection: protocol.AuthenticatorSelection{
 | 
				
			||||||
				UserVerification: "discouraged",
 | 
									UserVerification: "discouraged",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,11 +15,11 @@ func TestInit(t *testing.T) {
 | 
				
			|||||||
	setting.Domain = "domain"
 | 
						setting.Domain = "domain"
 | 
				
			||||||
	setting.AppName = "AppName"
 | 
						setting.AppName = "AppName"
 | 
				
			||||||
	setting.AppURL = "https://domain/"
 | 
						setting.AppURL = "https://domain/"
 | 
				
			||||||
	rpOrigin := "https://domain"
 | 
						rpOrigin := []string{"https://domain"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Init()
 | 
						Init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, setting.Domain, WebAuthn.Config.RPID)
 | 
						assert.Equal(t, setting.Domain, WebAuthn.Config.RPID)
 | 
				
			||||||
	assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName)
 | 
						assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName)
 | 
				
			||||||
	assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigin)
 | 
						assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigins)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,9 +14,9 @@ export function initUserAuthWebAuthn() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
 | 
					  $.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
 | 
				
			||||||
    .done((makeAssertionOptions) => {
 | 
					    .done((makeAssertionOptions) => {
 | 
				
			||||||
      makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge);
 | 
					      makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge);
 | 
				
			||||||
      for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
 | 
					      for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
 | 
				
			||||||
        makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id);
 | 
					        makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      navigator.credentials.get({
 | 
					      navigator.credentials.get({
 | 
				
			||||||
        publicKey: makeAssertionOptions.publicKey
 | 
					        publicKey: makeAssertionOptions.publicKey
 | 
				
			||||||
@@ -56,14 +56,14 @@ function verifyAssertion(assertedCredential) {
 | 
				
			|||||||
    type: 'POST',
 | 
					    type: 'POST',
 | 
				
			||||||
    data: JSON.stringify({
 | 
					    data: JSON.stringify({
 | 
				
			||||||
      id: assertedCredential.id,
 | 
					      id: assertedCredential.id,
 | 
				
			||||||
      rawId: bufferEncode(rawId),
 | 
					      rawId: encodeURLEncodedBase64(rawId),
 | 
				
			||||||
      type: assertedCredential.type,
 | 
					      type: assertedCredential.type,
 | 
				
			||||||
      clientExtensionResults: assertedCredential.getClientExtensionResults(),
 | 
					      clientExtensionResults: assertedCredential.getClientExtensionResults(),
 | 
				
			||||||
      response: {
 | 
					      response: {
 | 
				
			||||||
        authenticatorData: bufferEncode(authData),
 | 
					        authenticatorData: encodeURLEncodedBase64(authData),
 | 
				
			||||||
        clientDataJSON: bufferEncode(clientDataJSON),
 | 
					        clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
 | 
				
			||||||
        signature: bufferEncode(sig),
 | 
					        signature: encodeURLEncodedBase64(sig),
 | 
				
			||||||
        userHandle: bufferEncode(userHandle),
 | 
					        userHandle: encodeURLEncodedBase64(userHandle),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    contentType: 'application/json; charset=utf-8',
 | 
					    contentType: 'application/json; charset=utf-8',
 | 
				
			||||||
@@ -85,14 +85,21 @@ function verifyAssertion(assertedCredential) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Encode an ArrayBuffer into a base64 string.
 | 
					// Encode an ArrayBuffer into a URLEncoded base64 string.
 | 
				
			||||||
function bufferEncode(value) {
 | 
					function encodeURLEncodedBase64(value) {
 | 
				
			||||||
  return encode(value)
 | 
					  return encode(value)
 | 
				
			||||||
    .replace(/\+/g, '-')
 | 
					    .replace(/\+/g, '-')
 | 
				
			||||||
    .replace(/\//g, '_')
 | 
					    .replace(/\//g, '_')
 | 
				
			||||||
    .replace(/=/g, '');
 | 
					    .replace(/=/g, '');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Dccode a URLEncoded base64 to an ArrayBuffer string.
 | 
				
			||||||
 | 
					function decodeURLEncodedBase64(value) {
 | 
				
			||||||
 | 
					  return decode(value
 | 
				
			||||||
 | 
					    .replace(/_/g, '/')
 | 
				
			||||||
 | 
					    .replace(/-/g, '+'));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function webauthnRegistered(newCredential) {
 | 
					function webauthnRegistered(newCredential) {
 | 
				
			||||||
  const attestationObject = new Uint8Array(newCredential.response.attestationObject);
 | 
					  const attestationObject = new Uint8Array(newCredential.response.attestationObject);
 | 
				
			||||||
  const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
 | 
					  const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
 | 
				
			||||||
@@ -104,11 +111,11 @@ function webauthnRegistered(newCredential) {
 | 
				
			|||||||
    headers: {'X-Csrf-Token': csrfToken},
 | 
					    headers: {'X-Csrf-Token': csrfToken},
 | 
				
			||||||
    data: JSON.stringify({
 | 
					    data: JSON.stringify({
 | 
				
			||||||
      id: newCredential.id,
 | 
					      id: newCredential.id,
 | 
				
			||||||
      rawId: bufferEncode(rawId),
 | 
					      rawId: encodeURLEncodedBase64(rawId),
 | 
				
			||||||
      type: newCredential.type,
 | 
					      type: newCredential.type,
 | 
				
			||||||
      response: {
 | 
					      response: {
 | 
				
			||||||
        attestationObject: bufferEncode(attestationObject),
 | 
					        attestationObject: encodeURLEncodedBase64(attestationObject),
 | 
				
			||||||
        clientDataJSON: bufferEncode(clientDataJSON),
 | 
					        clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    dataType: 'json',
 | 
					    dataType: 'json',
 | 
				
			||||||
@@ -184,11 +191,11 @@ function webAuthnRegisterRequest() {
 | 
				
			|||||||
  }).done((makeCredentialOptions) => {
 | 
					  }).done((makeCredentialOptions) => {
 | 
				
			||||||
    $('#nickname').closest('div.field').removeClass('error');
 | 
					    $('#nickname').closest('div.field').removeClass('error');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge);
 | 
					    makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge);
 | 
				
			||||||
    makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id);
 | 
					    makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id);
 | 
				
			||||||
    if (makeCredentialOptions.publicKey.excludeCredentials) {
 | 
					    if (makeCredentialOptions.publicKey.excludeCredentials) {
 | 
				
			||||||
      for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
 | 
					      for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
 | 
				
			||||||
        makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id);
 | 
					        makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user