summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/authentication/webauthn/util.js
blob: 5f06c000afeb7872550eaef265231b7181c26261 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
export function supported() {
  return Boolean(
    navigator.credentials &&
      navigator.credentials.create &&
      navigator.credentials.get &&
      window.PublicKeyCredential,
  );
}

export function isHTTPS() {
  return window.location.protocol.startsWith('https');
}

export const FLOW_AUTHENTICATE = 'authenticate';
export const FLOW_REGISTER = 'register';

// adapted from https://stackoverflow.com/a/21797381/8204697
function base64ToBuffer(base64) {
  const binaryString = window.atob(base64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i += 1) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes.buffer;
}

// adapted from https://stackoverflow.com/a/9458996/8204697
function bufferToBase64(buffer) {
  if (typeof buffer === 'string') {
    return buffer;
  }

  let binary = '';
  const bytes = new Uint8Array(buffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i += 1) {
    binary += String.fromCharCode(bytes[i]);
  }
  return window.btoa(binary);
}

/**
 * Returns a copy of the given object with the id property converted to buffer
 *
 * @param {Object} param
 */
function convertIdToBuffer({ id, ...rest }) {
  return {
    ...rest,
    id: base64ToBuffer(id),
  };
}

/**
 * Returns a copy of the given array with all `id`s of the items converted to buffer
 *
 * @param {Array} items
 */
function convertIdsToBuffer(items) {
  return items.map(convertIdToBuffer);
}

/**
 * Returns an object with keys of the given props, and values from the given object converted to base64
 *
 * @param {String} obj
 * @param {Array} props
 */
function convertPropertiesToBase64(obj, props) {
  return props.reduce(
    (acc, property) => Object.assign(acc, { [property]: bufferToBase64(obj[property]) }),
    {},
  );
}

export function convertGetParams({ allowCredentials, challenge, ...rest }) {
  return {
    ...rest,
    ...(allowCredentials ? { allowCredentials: convertIdsToBuffer(allowCredentials) } : {}),
    challenge: base64ToBuffer(challenge),
  };
}

export function convertGetResponse(webauthnResponse) {
  return {
    type: webauthnResponse.type,
    id: webauthnResponse.id,
    rawId: bufferToBase64(webauthnResponse.rawId),
    response: convertPropertiesToBase64(webauthnResponse.response, [
      'clientDataJSON',
      'authenticatorData',
      'signature',
      'userHandle',
    ]),
    clientExtensionResults: webauthnResponse.getClientExtensionResults(),
  };
}

export function convertCreateParams({ challenge, user, excludeCredentials, ...rest }) {
  return {
    ...rest,
    challenge: base64ToBuffer(challenge),
    user: convertIdToBuffer(user),
    ...(excludeCredentials ? { excludeCredentials: convertIdsToBuffer(excludeCredentials) } : {}),
  };
}

export function convertCreateResponse(webauthnResponse) {
  return {
    type: webauthnResponse.type,
    id: webauthnResponse.id,
    rawId: bufferToBase64(webauthnResponse.rawId),
    clientExtensionResults: webauthnResponse.getClientExtensionResults(),
    response: convertPropertiesToBase64(webauthnResponse.response, [
      'clientDataJSON',
      'attestationObject',
    ]),
  };
}