webauthn: update login flow to support other 2fa methods
This commit is contained in:
parent
42a3e740ba
commit
31c2ef5279
13 changed files with 255 additions and 41 deletions
9
resources/scripts/api/account/webauthn/deleteKey.ts
Normal file
9
resources/scripts/api/account/webauthn/deleteKey.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import http from '@/api/http';
|
||||
|
||||
export default (id: number): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(`/api/client/account/webauthn/${id}`)
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
23
resources/scripts/api/account/webauthn/getWebauthn.ts
Normal file
23
resources/scripts/api/account/webauthn/getWebauthn.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import http from '@/api/http';
|
||||
|
||||
export interface Key {
|
||||
id: number;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
lastUsedAt: Date;
|
||||
}
|
||||
|
||||
export const rawDataToKey = (data: any): Key => ({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
createdAt: new Date(data.created_at),
|
||||
lastUsedAt: new Date(data.last_used_at) || new Date(),
|
||||
});
|
||||
|
||||
export default (): Promise<Key[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('/api/client/account/webauthn')
|
||||
.then(({ data }) => resolve((data.data || []).map((d: any) => rawDataToKey(d.attributes))))
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
51
resources/scripts/api/account/webauthn/login.ts
Normal file
51
resources/scripts/api/account/webauthn/login.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import http from '@/api/http';
|
||||
import { LoginResponse } from '@/api/auth/login';
|
||||
import { base64Decode, bufferDecode, bufferEncode, decodeCredentials } from '@/api/account/webauthn/registerKey';
|
||||
|
||||
export default (token: string, publicKey: PublicKeyCredentialRequestOptions): Promise<LoginResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(token);
|
||||
console.log(publicKey);
|
||||
const publicKeyCredential = Object.assign({}, publicKey);
|
||||
|
||||
publicKeyCredential.challenge = bufferDecode(base64Decode(publicKey.challenge.toString()));
|
||||
if (publicKey.allowCredentials) {
|
||||
publicKeyCredential.allowCredentials = decodeCredentials(publicKey.allowCredentials);
|
||||
}
|
||||
|
||||
navigator.credentials.get({
|
||||
publicKey: publicKeyCredential,
|
||||
}).then((c) => {
|
||||
if (c === null) {
|
||||
return;
|
||||
}
|
||||
const credential = c as PublicKeyCredential;
|
||||
const response = credential.response as AuthenticatorAssertionResponse;
|
||||
|
||||
const data = {
|
||||
confirmation_token: token,
|
||||
|
||||
data: JSON.stringify({
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: bufferEncode(credential.rawId),
|
||||
|
||||
response: {
|
||||
authenticatorData: bufferEncode(response.authenticatorData),
|
||||
clientDataJSON: bufferEncode(response.clientDataJSON),
|
||||
signature: bufferEncode(response.signature),
|
||||
userHandle: response.userHandle ? bufferEncode(response.userHandle) : null,
|
||||
},
|
||||
}),
|
||||
};
|
||||
console.log(data);
|
||||
|
||||
http.post('/auth/login/checkpoint/key', data).then(response => {
|
||||
return resolve({
|
||||
complete: response.data.complete,
|
||||
intended: response.data.data?.intended || undefined,
|
||||
});
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
73
resources/scripts/api/account/webauthn/registerKey.ts
Normal file
73
resources/scripts/api/account/webauthn/registerKey.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import http from '@/api/http';
|
||||
import { Key, rawDataToKey } from '@/api/account/webauthn/getWebauthn';
|
||||
|
||||
export const base64Decode = (input: string): string => {
|
||||
input = input.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const pad = input.length % 4;
|
||||
if (pad) {
|
||||
if (pad === 1) {
|
||||
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
|
||||
}
|
||||
input += new Array(5 - pad).join('=');
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
export const bufferDecode = (value: string): ArrayBuffer => {
|
||||
return Uint8Array.from(window.atob(value), c => c.charCodeAt(0));
|
||||
};
|
||||
|
||||
export const bufferEncode = (value: ArrayBuffer): string => {
|
||||
// @ts-ignore
|
||||
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(value)));
|
||||
};
|
||||
|
||||
export const decodeCredentials = (credentials: PublicKeyCredentialDescriptor[]) => {
|
||||
return credentials.map(c => {
|
||||
return {
|
||||
id: bufferDecode(base64Decode(c.id.toString())),
|
||||
type: c.type,
|
||||
transports: c.transports,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export default (name: string): Promise<Key> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('/api/client/account/webauthn/register').then((res) => {
|
||||
const publicKey = res.data.public_key;
|
||||
const publicKeyCredential = Object.assign({}, publicKey);
|
||||
|
||||
publicKeyCredential.user.id = bufferDecode(publicKey.user.id);
|
||||
publicKeyCredential.challenge = bufferDecode(base64Decode(publicKey.challenge));
|
||||
if (publicKey.excludeCredentials) {
|
||||
publicKeyCredential.excludeCredentials = decodeCredentials(publicKey.excludeCredentials);
|
||||
}
|
||||
|
||||
return navigator.credentials.create({
|
||||
publicKey: publicKeyCredential,
|
||||
});
|
||||
}).then((c) => {
|
||||
if (c === null) {
|
||||
return;
|
||||
}
|
||||
const credential = c as PublicKeyCredential;
|
||||
const response = credential.response as AuthenticatorAttestationResponse;
|
||||
|
||||
http.post('/api/client/account/webauthn/register', {
|
||||
name: name,
|
||||
|
||||
register: JSON.stringify({
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: bufferEncode(credential.rawId),
|
||||
|
||||
response: {
|
||||
attestationObject: bufferEncode(response.attestationObject),
|
||||
clientDataJSON: bufferEncode(response.clientDataJSON),
|
||||
},
|
||||
}),
|
||||
}).then(({ data }) => resolve(rawDataToKey(data.attributes))).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue