WebAuthnでログインできないのを修正 (#6327)

Resolve #6319
This commit is contained in:
Satsuki Yanagi 2020-05-09 08:20:22 +09:00 committed by GitHub
parent 5225ad822c
commit c348688eeb
4 changed files with 46 additions and 32 deletions

View file

@ -414,7 +414,7 @@ passwordMatched: "一致しました"
passwordNotMatched: "一致していません" passwordNotMatched: "一致していません"
signinWith: "{x}でログイン" signinWith: "{x}でログイン"
signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
tapSecurityKey: "セキュリティキーにタッチ" tapSecurityKey: "セキュリティキーにタッチ"
or: "もしくは" or: "もしくは"
uiLanguage: "UIの表示言語" uiLanguage: "UIの表示言語"
groupInvited: "グループに招待されました" groupInvited: "グループに招待されました"

View file

@ -51,7 +51,7 @@ import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue'; import MkInput from './ui/input.vue';
import i18n from '../i18n'; import i18n from '../i18n';
import { apiUrl, host } from '../config'; import { apiUrl, host } from '../config';
import { hexifyAB } from '../scripts/2fa'; import { byteify, hexify } from '../scripts/2fa';
export default Vue.extend({ export default Vue.extend({
i18n, i18n,
@ -121,14 +121,9 @@ export default Vue.extend({
this.queryingKey = true; this.queryingKey = true;
return navigator.credentials.get({ return navigator.credentials.get({
publicKey: { publicKey: {
challenge: Buffer.from( challenge: byteify(this.challengeData.challenge, 'base64'),
this.challengeData.challenge
.replace(/\-/g, '+')
.replace(/_/g, '/'),
'base64'
),
allowCredentials: this.challengeData.securityKeys.map(key => ({ allowCredentials: this.challengeData.securityKeys.map(key => ({
id: Buffer.from(key.id, 'hex'), id: byteify(key.id, 'hex'),
type: 'public-key', type: 'public-key',
transports: ['usb', 'nfc', 'ble', 'internal'] transports: ['usb', 'nfc', 'ble', 'internal']
})), })),
@ -143,9 +138,9 @@ export default Vue.extend({
return this.$root.api('signin', { return this.$root.api('signin', {
username: this.username, username: this.username,
password: this.password, password: this.password,
signature: hexifyAB(credential.response.signature), signature: hexify(credential.response.signature),
authenticatorData: hexifyAB(credential.response.authenticatorData), authenticatorData: hexify(credential.response.authenticatorData),
clientDataJSON: hexifyAB(credential.response.clientDataJSON), clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id, credentialId: credential.id,
challengeId: this.challengeData.challengeId challengeId: this.challengeData.challengeId
}); });

View file

@ -22,12 +22,12 @@
<mk-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0">{{ $t('passwordLessLogin') }}</mk-switch> <mk-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0">{{ $t('passwordLessLogin') }}</mk-switch>
<mk-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</mk-info> <mk-info warn v-if="registration && registration.error">{{ $t('error') }} {{ registration.error }}</mk-info>
<mk-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('_2fa.registerKey') }}</mk-button> <mk-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('_2fa.registerKey') }}</mk-button>
<ol v-if="registration && !registration.error"> <ol v-if="registration && !registration.error">
<li v-if="registration.stage >= 0"> <li v-if="registration.stage >= 0">
{{ $t('activate-key') }} {{ $t('tapSecurityKey') }}
<fa icon="spinner" pulse fixed-width v-if="registration.saving && registration.stage == 0" /> <fa icon="spinner" pulse fixed-width v-if="registration.saving && registration.stage == 0" />
</li> </li>
<li v-if="registration.stage >= 1"> <li v-if="registration.stage >= 1">
@ -67,16 +67,12 @@ import Vue from 'vue';
import { faLock } from '@fortawesome/free-solid-svg-icons'; import { faLock } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { hostname } from '../../config'; import { hostname } from '../../config';
import { hexifyAB } from '../../scripts/2fa'; import { byteify, hexify, stringify } from '../../scripts/2fa';
import MkButton from '../../components/ui/button.vue'; import MkButton from '../../components/ui/button.vue';
import MkInfo from '../../components/ui/info.vue'; import MkInfo from '../../components/ui/info.vue';
import MkInput from '../../components/ui/input.vue'; import MkInput from '../../components/ui/input.vue';
import MkSwitch from '../../components/ui/switch.vue'; import MkSwitch from '../../components/ui/switch.vue';
function stringifyAB(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
export default Vue.extend({ export default Vue.extend({
i18n, i18n,
components: { components: {
@ -157,8 +153,8 @@ export default Vue.extend({
name: this.keyName, name: this.keyName,
challengeId: this.registration.challengeId, challengeId: this.registration.challengeId,
// we convert each 16 bits to a string to serialise // we convert each 16 bits to a string to serialise
clientDataJSON: stringifyAB(this.registration.credential.response.clientDataJSON), clientDataJSON: stringify(this.registration.credential.response.clientDataJSON),
attestationObject: hexifyAB(this.registration.credential.response.attestationObject) attestationObject: hexify(this.registration.credential.response.attestationObject)
}).then(key => { }).then(key => {
this.registration = null; this.registration = null;
key.lastUsed = new Date(); key.lastUsed = new Date();
@ -208,18 +204,13 @@ export default Vue.extend({
challengeId: registration.challengeId, challengeId: registration.challengeId,
stage: 0, stage: 0,
publicKeyOptions: { publicKeyOptions: {
challenge: Uint8Array.from( challenge: byteify(registration.challenge, 'base64'),
atob(registration.challenge
.replace(/\-/g, '+')
.replace(/_/g, '/')),
x => x.charCodeAt(0),
),
rp: { rp: {
id: hostname, id: hostname,
name: 'Misskey' name: 'Misskey'
}, },
user: { user: {
id: Uint8Array.from(this.$store.state.i.id, c => c.charCodeAt(0)), id: byteify(this.$store.state.i.id, 'ascii'),
name: this.$store.state.i.username, name: this.$store.state.i.username,
displayName: this.$store.state.i.name, displayName: this.$store.state.i.name,
}, },

View file

@ -1,5 +1,33 @@
export function hexifyAB(buffer) { export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') {
return Array.from(new Uint8Array(buffer)) switch (encoding) {
.map(item => item.toString(16).padStart(2, '0')) case 'ascii':
.join(''); return Uint8Array.from(data, c => c.charCodeAt(0));
case 'base64':
return Uint8Array.from(
atob(
data
.replace(/-/g, '+')
.replace(/_/g, '/')
),
c => c.charCodeAt(0)
);
case 'hex':
return new Uint8Array(
data
.match(/.{1,2}/g)
.map(byte => parseInt(byte, 16))
);
}
}
export function hexify(buffer: ArrayBuffer) {
return Array.from(new Uint8Array(buffer))
.reduce(
(str, byte) => str + byte.toString(16).padStart(2, '0'),
''
);
}
export function stringify(buffer: ArrayBuffer) {
return String.fromCharCode(... new Uint8Array(buffer));
} }