@standardnotes/authenticator 2.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/lib/otp.js ADDED
@@ -0,0 +1,184 @@
1
+ import { base32ToHex, bufToHex, decToHex, hextoBuf, hexToBytes, leftpad } from '@Lib/utils'
2
+ export { parseKeyUri, secretPattern } from '@Lib/utils'
3
+
4
+ class Hotp {
5
+ /**
6
+ * Generate a counter based One Time Password
7
+ *
8
+ * @return {String} the one time password
9
+ *
10
+ * Arguments:
11
+ *
12
+ * args
13
+ * key - Key for the one time password. This should be unique and secret for
14
+ * every user as this is the seed that is used to calculate the HMAC
15
+ *
16
+ * counter - Counter value. This should be stored by the application, must
17
+ * be user specific, and be incremented for each request.
18
+ *
19
+ */
20
+ async gen(secret, opt) {
21
+ var key = base32ToHex(secret) || ''
22
+ opt = opt || {}
23
+ var counter = opt.counter || 0
24
+
25
+ var hexCounter = leftpad(decToHex(counter), 16, '0')
26
+ var digest = await this.createHmac('SHA-1', key, hexCounter)
27
+ var h = hexToBytes(digest)
28
+
29
+ // Truncate
30
+ var offset = h[h.length - 1] & 0xf
31
+ var v =
32
+ ((h[offset] & 0x7f) << 24) |
33
+ ((h[offset + 1] & 0xff) << 16) |
34
+ ((h[offset + 2] & 0xff) << 8) |
35
+ (h[offset + 3] & 0xff)
36
+
37
+ v = (v % 1000000) + ''
38
+
39
+ return Array(7 - v.length).join('0') + v
40
+ }
41
+
42
+ /**
43
+ * Check a One Time Password based on a counter.
44
+ *
45
+ * @return {Object} null if failure, { delta: # } on success
46
+ * delta is the time step difference between the client and the server
47
+ *
48
+ * Arguments:
49
+ *
50
+ * args
51
+ * key - Key for the one time password. This should be unique and secret for
52
+ * every user as it is the seed used to calculate the HMAC
53
+ *
54
+ * token - Passcode to validate.
55
+ *
56
+ * window - The allowable margin for the counter. The function will check
57
+ * 'W' codes in the future against the provided passcode. Note,
58
+ * it is the calling applications responsibility to keep track of
59
+ * 'W' and increment it for each password check, and also to adjust
60
+ * it accordingly in the case where the client and server become
61
+ * out of sync (second argument returns non zero).
62
+ * E.g. if W = 100, and C = 5, this function will check the passcode
63
+ * against all One Time Passcodes between 5 and 105.
64
+ *
65
+ * Default - 50
66
+ *
67
+ * counter - Counter value. This should be stored by the application, must
68
+ * be user specific, and be incremented for each request.
69
+ *
70
+ */
71
+ async verify(token, key, opt) {
72
+ opt = opt || {}
73
+ var window = opt.window || 50
74
+ var counter = opt.counter || 0
75
+
76
+ // Now loop through from C to C + W to determine if there is
77
+ // a correct code
78
+ for (var i = counter - window; i <= counter + window; ++i) {
79
+ opt.counter = i
80
+ if ((await this.gen(key, opt)) === token) {
81
+ // We have found a matching code, trigger callback
82
+ // and pass offset
83
+ return { delta: i - counter }
84
+ }
85
+ }
86
+
87
+ // If we get to here then no codes have matched, return null
88
+ return null
89
+ }
90
+
91
+ async createHmac(alg, key, str) {
92
+ const hmacKey = await window.crypto.subtle.importKey(
93
+ 'raw', // raw format of the key - should be Uint8Array
94
+ hextoBuf(key),
95
+ {
96
+ // algorithm details
97
+ name: 'HMAC',
98
+ hash: { name: alg },
99
+ },
100
+ false, // export = false
101
+ ['sign'], // what this key can do
102
+ )
103
+ const sig = await window.crypto.subtle.sign('HMAC', hmacKey, hextoBuf(str))
104
+ return bufToHex(sig)
105
+ }
106
+ }
107
+
108
+ export const hotp = new Hotp()
109
+
110
+ class Totp {
111
+ /**
112
+ * Generate a time based One Time Password
113
+ *
114
+ * @return {String} the one time password
115
+ *
116
+ * Arguments:
117
+ *
118
+ * args
119
+ * key - Key for the one time password. This should be unique and secret for
120
+ * every user as it is the seed used to calculate the HMAC
121
+ *
122
+ * time - The time step of the counter. This must be the same for
123
+ * every request and is used to calculat C.
124
+ *
125
+ * Default - 30
126
+ *
127
+ */
128
+ async gen(key, opt) {
129
+ opt = opt || {}
130
+ var time = opt.time || 30
131
+ var _t = Date.now()
132
+
133
+ // Determine the value of the counter, C
134
+ // This is the number of time steps in seconds since T0
135
+ opt.counter = Math.floor(_t / 1000 / time)
136
+
137
+ return hotp.gen(key, opt)
138
+ }
139
+
140
+ /**
141
+ * Check a One Time Password based on a timer.
142
+ *
143
+ * @return {Object} null if failure, { delta: # } on success
144
+ * delta is the time step difference between the client and the server
145
+ *
146
+ * Arguments:
147
+ *
148
+ * args
149
+ * key - Key for the one time password. This should be unique and secret for
150
+ * every user as it is the seed used to calculate the HMAC
151
+ *
152
+ * token - Passcode to validate.
153
+ *
154
+ * window - The allowable margin for the counter. The function will check
155
+ * 'W' codes either side of the provided counter. Note,
156
+ * it is the calling applications responsibility to keep track of
157
+ * 'W' and increment it for each password check, and also to adjust
158
+ * it accordingly in the case where the client and server become
159
+ * out of sync (second argument returns non zero).
160
+ * E.g. if W = 5, and C = 1000, this function will check the passcode
161
+ * against all One Time Passcodes between 995 and 1005.
162
+ *
163
+ * Default - 6
164
+ *
165
+ * time - The time step of the counter. This must be the same for
166
+ * every request and is used to calculate C.
167
+ *
168
+ * Default - 30
169
+ *
170
+ */
171
+ async verify(token, key, opt) {
172
+ opt = opt || {}
173
+ var time = opt.time || 30
174
+ var _t = Date.now()
175
+
176
+ // Determine the value of the counter, C
177
+ // This is the number of time steps in seconds since T0
178
+ opt.counter = Math.floor(_t / 1000 / time)
179
+
180
+ return hotp.verify(token, key, opt)
181
+ }
182
+ }
183
+
184
+ export const totp = new Totp()
@@ -0,0 +1,185 @@
1
+ const base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
2
+ export const secretPattern = `^[${base32chars}]{16,}$`
3
+
4
+ export function hexToBytes(hex) {
5
+ var bytes = []
6
+ for (var c = 0, C = hex.length; c < C; c += 2) {
7
+ bytes.push(parseInt(hex.substr(c, 2), 16))
8
+ }
9
+ return bytes
10
+ }
11
+
12
+ export function decToHex(s) {
13
+ return (s < 15.5 ? '0' : '') + Math.round(s).toString(16)
14
+ }
15
+
16
+ export function bufToHex(buf) {
17
+ return Array.prototype.map.call(new Uint8Array(buf), (x) => ('00' + x.toString(16)).slice(-2)).join('')
18
+ }
19
+
20
+ export function hextoBuf(hex) {
21
+ var view = new Uint8Array(hex.length / 2)
22
+
23
+ for (var i = 0; i < hex.length; i += 2) {
24
+ view[i / 2] = parseInt(hex.substring(i, i + 2), 16)
25
+ }
26
+
27
+ return view.buffer
28
+ }
29
+
30
+ export function base32ToHex(base32) {
31
+ var bits, chunk, hex, i, val
32
+ bits = ''
33
+ hex = ''
34
+ i = 0
35
+ while (i < base32.length) {
36
+ val = base32chars.indexOf(base32.charAt(i).toUpperCase())
37
+ bits += leftpad(val.toString(2), 5, '0')
38
+ i++
39
+ }
40
+ i = 0
41
+ while (i + 4 <= bits.length) {
42
+ chunk = bits.substr(i, 4)
43
+ hex = hex + parseInt(chunk, 2).toString(16)
44
+ i += 4
45
+ }
46
+ return hex
47
+ }
48
+
49
+ export function leftpad(str, len, pad) {
50
+ if (len + 1 >= str.length) {
51
+ str = Array(len + 1 - str.length).join(pad) + str
52
+ }
53
+ return str
54
+ }
55
+
56
+ /**
57
+ * This function takes an otpauth:// style key URI and parses it into an object with keys for the
58
+ * various parts of the URI
59
+ *
60
+ * @param {String} uri The otpauth:// uri that you want to parse
61
+ *
62
+ * @return {Object} The parsed URI or null on failure. The URI object looks like this:
63
+ *
64
+ * {
65
+ * type: 'totp',
66
+ * label: { issuer: 'ACME Co', account: 'jane@example.com' },
67
+ * query: {
68
+ * secret: 'JBSWY3DPEHPK3PXP',
69
+ * digits: '6'
70
+ * }
71
+ * }
72
+ *
73
+ * @see <a href="https://github.com/google/google-authenticator/wiki/Key-Uri-Format">otpauth Key URI Format</a>
74
+ */
75
+ export function parseKeyUri(uri) {
76
+ // Quick sanity check
77
+ if (typeof uri !== 'string' || uri.length < 7) return null
78
+
79
+ // I would like to just use new URL(), but the behavior is different between node and browsers, so
80
+ // we have to do some of the work manually with regex.
81
+ const parts = /otpauth:\/\/([A-Za-z]+)\/([^?]+)\??(.*)?/i.exec(uri)
82
+
83
+ if (!parts || parts.length < 3) {
84
+ return null
85
+ }
86
+
87
+ // eslint-disable-next-line no-unused-vars
88
+ const [fullUri, type, fullLabel] = parts
89
+
90
+ // Sanity check type and label
91
+ if (!type || !fullLabel) {
92
+ return null
93
+ }
94
+
95
+ // Parse the label
96
+ const decodedLabel = decodeURIComponent(fullLabel)
97
+
98
+ const labelParts = decodedLabel.split(/: ?/)
99
+
100
+ const label =
101
+ labelParts && labelParts.length === 2
102
+ ? { issuer: labelParts[0], account: labelParts[1] }
103
+ : { issuer: '', account: decodedLabel }
104
+
105
+ // Parse query string
106
+ const qs = parts[3] ? new URLSearchParams(parts[3]) : []
107
+
108
+ const query = [...qs].reduce((acc, [key, value]) => {
109
+ acc[key] = value
110
+
111
+ return acc
112
+ }, {})
113
+
114
+ // Returned the parsed parts of the URI
115
+ return { type: type.toLowerCase(), label, query }
116
+ }
117
+
118
+ /**
119
+ * Converts a hex color string to an object containing RGB values.
120
+ */
121
+ export function hexColorToRGB(hexColor) {
122
+ // Expand the shorthand form (e.g. "0AB") to full form (e.g. "00AABB")
123
+ const shortHandFormRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
124
+ hexColor = hexColor.replace(shortHandFormRegex, function (m, red, green, blue) {
125
+ return red + red + green + green + blue + blue
126
+ })
127
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor)
128
+ return result
129
+ ? {
130
+ red: parseInt(result[1], 16),
131
+ green: parseInt(result[2], 16),
132
+ blue: parseInt(result[3], 16),
133
+ }
134
+ : null
135
+ }
136
+
137
+ export const defaultBgColor = '#FFF'
138
+
139
+ /**
140
+ * Gets the color variable to be used based on the calculated constrast of a color.
141
+ */
142
+ export function getVarColorForContrast(backgroundColor) {
143
+ const styleKitColors = {
144
+ foreground: '--sn-stylekit-contrast-foreground-color',
145
+ background: '--sn-stylekit-contrast-background-color',
146
+ }
147
+ if (!backgroundColor) {
148
+ return styleKitColors.foreground
149
+ }
150
+ const colorContrast = Math.round(
151
+ (parseInt(backgroundColor.red) * 299 +
152
+ parseInt(backgroundColor.green) * 587 +
153
+ parseInt(backgroundColor.blue) * 114) /
154
+ 1000,
155
+ )
156
+ return colorContrast > 70 ? styleKitColors.background : styleKitColors.foreground
157
+ }
158
+
159
+ function getPropertyValue(document, propertyName) {
160
+ return getComputedStyle(document.documentElement).getPropertyValue(propertyName).trim().toUpperCase()
161
+ }
162
+
163
+ export const contextualColors = ['info', 'success', 'neutral', 'warning']
164
+
165
+ export function getContextualColor(document, colorName) {
166
+ if (!contextualColors.includes(colorName)) {
167
+ return
168
+ }
169
+
170
+ return getPropertyValue(document, `--sn-stylekit-${colorName}-color`)
171
+ }
172
+
173
+ export function getEntryColor(document, entry) {
174
+ const { color } = entry
175
+
176
+ if (!contextualColors.includes(color)) {
177
+ return color
178
+ }
179
+
180
+ return getContextualColor(document, color)
181
+ }
182
+
183
+ export function getAllContextualColors(document) {
184
+ return contextualColors.map((colorName) => getContextualColor(document, colorName))
185
+ }