From e4146b8205c7493f59adc70526ea4940edb8eced Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:06:05 -0400 Subject: [PATCH 01/10] jwt - Added @tsndr/cloudflare-worker-jwt and auth error logging --- handlers.js | 2 +- modules/auth.js | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/handlers.js b/handlers.js index 461d981..0b391af 100644 --- a/handlers.js +++ b/handlers.js @@ -1,4 +1,4 @@ -const jwt = require('jsonwebtoken') +import jwt from '@tsndr/cloudflare-worker-jwt' const { totp } = require('otplib') const { v4: uuidv4 } = require('uuid') diff --git a/modules/auth.js b/modules/auth.js index cc583ec..d2a5072 100644 --- a/modules/auth.js +++ b/modules/auth.js @@ -1,22 +1,27 @@ -const jwt = require('jsonwebtoken') +import jwt from '@tsndr/cloudflare-worker-jwt' const { HTTPError, respondError } = require('./utils') const { getKVUser } = require('./users') -// process.env is not avail in workers, direct access like KV namespaces and secrets - -const verifyJWT = (headers) => { +const verifyJWT = async (headers) => { const token = headers.get('portunus-jwt') if (!token) { + console.error("Error: No portunus-jwt found in headers.") throw new HTTPError('Auth requires portunus-jwt', 403) } let access = {} try { - access = jwt.verify(token, TOKEN_SECRET) - } catch (_) { + await jwt.verify(token, TOKEN_SECRET) + + // Decoding token + const { payload } = jwt.decode(token) + access = payload + } catch (error) { + console.error("Error: Invalid portunus-jwt.", error) throw new HTTPError('Invalid portunus-jwt', 403) } if (!access.email) { + console.error("Error: No email in portunus-jwt.") throw new HTTPError('Invalid portunus-jwt: no email', 403) } return access @@ -24,7 +29,12 @@ const verifyJWT = (headers) => { const verifyUser = async (access) => { const user = await getKVUser(access.email) + if (!user) { + console.error("Error: No user found with this email.") + throw new HTTPError('No user found with this email', 404) + } if (user.jwt_uuid !== access.jwt_uuid) { + console.error("Error: UUID mismatch in portunus-jwt.") throw new HTTPError('Invalid portunus-jwt: UUID mismatch', 403) } @@ -34,9 +44,10 @@ const verifyUser = async (access) => { module.exports.withRequireUser = async (req) => { const { headers } = req try { - const access = verifyJWT(headers) + const access = await verifyJWT(headers) req.user = await verifyUser(access) } catch (err) { - return respondError(err) + console.error("Error: Failed to verify user.", err) + return respondError(req) } } From f710b0c6d225e5309b5338f5acbb5a553dfe5194 Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:10:15 -0400 Subject: [PATCH 02/10] otp - Updated totp logic --- handlers.js | 187 ++++++++++++++++++++++++++++--------------------- modules/otp.js | 51 ++++++++++++++ 2 files changed, 160 insertions(+), 78 deletions(-) create mode 100644 modules/otp.js diff --git a/handlers.js b/handlers.js index 0b391af..4345bd1 100644 --- a/handlers.js +++ b/handlers.js @@ -1,8 +1,7 @@ import jwt from '@tsndr/cloudflare-worker-jwt' -const { totp } = require('otplib') -const { v4: uuidv4 } = require('uuid') +const { generateOTP, verifyOTP } = require('./modules/otp.js'); -totp.options = { step: 60 * 5 } // 5 minutes for the OTPs +const { v4: uuidv4 } = require('uuid') const { HTTPError, respondError, respondJSON } = require('./modules/utils') const { @@ -490,6 +489,7 @@ module.exports.addUserToAdmin = async ({ } await addUserToAdmin({ team, user: kvUser }) + return respondJSON({ payload: { key: kvUser.key } }) } catch (err) { @@ -572,85 +572,101 @@ module.exports.getEnv = async ({ user, query }) => { } } -// Auth handlers module.exports.getOTP = async ({ query, url, headers, cf = {} }) => { - const { user, origin } = query // user is email - if (!user || !EMAIL_REGEXP.test(user)) { - return respondError(new HTTPError('User email not supplied', 400)) - } - let u = await fetchUser(user) - if (!u) { - u = await createUser(user) - } else if (!u.otp_secret) { - // legacy user without otp_secret - u.otp_secret = uuidv4() - if (u.otp_secret === u.jwt_uuid) { - // Note: this shouldn't happen anyway - throw new Error('OTP secret and JWT UUID are the same') - } - u.updated = new Date() - await updateUser(u) + try { + const { user, origin } = query + if (!user || !EMAIL_REGEXP.test(user)) { + throw new HTTPError('User email not supplied', 400) + } + let u = await fetchUser(user) + if (!u) { + u = await createUser(user) + } else if (!u.otp_secret) { + u.otp_secret = uuidv4() + if (u.otp_secret === u.jwt_uuid) { + throw new Error('OTP secret and JWT UUID are the same') + } + u.updated = new Date() + await updateUser(u) + } + let otp; + otp = await generateOTP(u.otp_secret); + const timeRemaining = 30 - (Math.floor(Date.now() / 1000) % 30); + const expiresAt = new Date(Date.now() + timeRemaining * 1000); + const { origin: _origin } = new URL(url) + const defaultOrigin = `${_origin}/login` + const locale = (headers.get('Accept-Language') || '').split(',')[0] || 'en' + const timeZone = cf.timezone || 'UTC' + const plainEmail = [ + `OTP: ${otp}`, + `Magic-Link: ${origin || defaultOrigin}?user=${user}&otp=${otp}`, + `Expires at: ${expiresAt.toLocaleString(locale, { + timeZone, + timeZoneName: 'long', + })}`, + ].join('\n') + await fetch('https://api.sendgrid.com/v3/mail/send', { + method: 'POST', + headers: { + authorization: `Bearer ${MAIL_PASS}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + personalizations: [{ to: [{ email: u.email }] }], + from: { email: 'dev@ashishjullia.com' }, + subject: 'Portunus Login OTP/Magic-Link', + content: [ + { + type: 'text/plain', + value: plainEmail, + }, + ], + }), + }) + return respondJSON({ payload: { message: `OTP/Magic-Link sent to ${user}` } }) + } catch (error) { + console.error("Error in getOTP: ", error); + throw error; } - // Use time-based OTP to avoid storing them in deta/KV - const otp = totp.generate(u.otp_secret) - const expiresAt = new Date(Date.now() + totp.timeRemaining() * 1000) - // TODO: tricky for local dev as the origin is mapped to remote cloudflare worker - const { origin: _origin } = new URL(url) - const defaultOrigin = `${_origin}/login` - // obtain locale and timezone from request for email `expiresAt` formatting - const locale = (headers.get('Accept-Language') || '').split(',')[0] || 'en' - const timeZone = cf.timezone || 'UTC' - // send email with OTP - // TODO: need to manually remove sendgrid tracking https://app.sendgrid.com/settings/tracking - // TODO: need to programmatically turn it off always https://stackoverflow.com/a/63360103/158111 - // TODO: setup own SMTP server (mailu) or AWS SES for much cheaper delivery - const plainEmail = [ - `OTP: ${otp}`, - `Magic-Link: ${origin || defaultOrigin}?user=${user}&otp=${otp}`, - `Expires at: ${expiresAt.toLocaleString(locale, { - timeZone, - timeZoneName: 'long', - })}`, - ].join('\n') - await fetch('https://api.sendgrid.com/v3/mail/send', { - method: 'POST', - headers: { - authorization: `Bearer ${MAIL_PASS}`, - 'content-type': 'application/json', - }, - body: JSON.stringify({ - personalizations: [{ to: [{ email: u.email }] }], - from: { email: 'dev@mindswire.com' }, - subject: 'Portunus Login OTP/Magic-Link', - content: [ - { - type: 'text/plain', - value: plainEmail, - }, - ], - }), - }) - return respondJSON({ payload: { message: `OTP/Magic-Link sent to ${user}` } }) } module.exports.login = async ({ query }) => { - const { user, otp } = query - if (!user || !otp) { - return respondError(new HTTPError('User or OTP not supplied', 400)) - } - const { email, jwt_uuid, otp_secret } = (await fetchUser(user)) || { - teams: [], - } - if (!email || !otp_secret) { - return respondError(new HTTPError(`${user} not found`, 404)) - } - const isValid = totp.verify({ secret: otp_secret, token: otp }) - if (!isValid) { - return respondError(new HTTPError('Invalid OTP', 403)) + try { + const { user, otp } = query; + if (!user || !otp) { + console.error('User or OTP not supplied'); // I added + return respondError(new HTTPError('User or OTP not supplied', 400)); + } + const { email, jwt_uuid, otp_secret } = (await fetchUser(user)) || { + teams: [], + }; + if (!email || !otp_secret) { + console.error(`${user} not found`); // I added + return respondError(new HTTPError(`${user} not found`, 404)); + } + + const isValid = await verifyOTP(otp, otp_secret); + console.log("Valid or not:",isValid); + console.log("OTP during login:",otp); + console.log("otp secret during login:",otp_secret); + console.log("user values during login:", await fetchUser(user)); + if (!isValid) { + console.error('Invalid OTP'); // I added + return respondError(new HTTPError('Invalid OTP', 403)); + } + + try { + const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET); + return respondJSON({ payload: { jwt: token } }); + } catch (err) { + console.error('Error generating JWT:', err); // I added + return respondError(new HTTPError('JWT token generation failed', 500)); + } + } catch (err) { + console.error('Error in login function:', err); // I added + return respondError(new HTTPError('Internal server error', 500)); } - const token = jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) - return respondJSON({ payload: { jwt: token } }) -} +}; // legacy direct JWT through email module.exports.getToken = async ({ query }) => { @@ -663,7 +679,22 @@ module.exports.getToken = async ({ query }) => { // return respondError(new HTTPError(`${user} not found`, 404)) // } - const token = jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) + // const token = jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) + // const token = await new jose.SignJWT({ email, jwt_uuid }).sign(TOKEN_SECRET) + const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) + + // const sharedSecret = Uint8Array.from(TOKEN_SECRET, c => c.charCodeAt(0)) + // // Import your TOKEN_SECRET as a key + // const key = await importKey( + // sharedSecret, + // { name: 'HMAC', hash: 'SHA-256' }, + // 'raw', + // ['sign', 'verify'] + // ) + // // Create a new SignJWT object + // const jwt = new SignJWT({ email, jwt_uuid }) + // // Sign the JWT with your key + // const token = await jwt.sign(key) try { await fetch('https://api.sendgrid.com/v3/mail/send', { @@ -674,7 +705,7 @@ module.exports.getToken = async ({ query }) => { }, body: JSON.stringify({ personalizations: [{ to: [{ email }] }], - from: { email: 'dev@mindswire.com' }, + from: { email: 'dev@ashishjullia.com' }, subject: 'print-env token', content: [{ type: 'text/plain', value: token }], }), diff --git a/modules/otp.js b/modules/otp.js new file mode 100644 index 0000000..ddaef75 --- /dev/null +++ b/modules/otp.js @@ -0,0 +1,51 @@ +async function generateOTP(secret, timePeriodOffset = 0) { + // Get the current time + const time = Math.floor(Date.now() / 1000) + + // Calculate the time period with the offset + const timePeriod = Math.floor(time / 30) + timePeriodOffset; + + const timeBuffer = new ArrayBuffer(8); + (new DataView(timeBuffer)).setBigUint64(0, BigInt(timePeriod), false); + + + // Create a key from the secret + const key = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(secret), + { name: 'HMAC', hash: 'SHA-1' }, + false, + ['sign'] + ) + + // Sign the time buffer with the key + const signatureBuffer = await crypto.subtle.sign('HMAC', key, timeBuffer) + const signature = new Uint8Array(signatureBuffer) + + // Take the last 4 bits of the signature + const offset = signature[signature.length - 1] & 0xf + + // Take 4 bytes from the signature starting at the offset + const otp = (signature[offset] & 0x7f) << 24 | + (signature[offset + 1] & 0xff) << 16 | + (signature[offset + 2] & 0xff) << 8 | + (signature[offset + 3] & 0xff) + + // Take the last 6 digits of the otp + const otpStr = (otp % 1000000).toString().padStart(6, '0') + + return otpStr + } + + async function verifyOTP(otp, secret) { + // Check the OTP for the current time period and the previous and next time periods + const otps = [ + await generateOTP(secret, -1), + await generateOTP(secret, 0), + await generateOTP(secret, 1) + ]; + + return otps.includes(otp); +} + +module.exports = { generateOTP, verifyOTP }; From 1a999c07e34afe547e8cc33af798a1873a78daa4 Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:14:31 -0400 Subject: [PATCH 03/10] package - Updated --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 2d0802e..d451f42 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,10 @@ "prettier": "^2.2.1" }, "dependencies": { - "deta-worker": "^0.2.0-alpha", + "@tsndr/cloudflare-worker-jwt": "^2.2.1", "eslint-plugin-jest": "^25.3.4", "itty-router": "^2.5.2", "itty-router-extras": "^0.4.2", - "jsonwebtoken": "^8.5.1", - "otplib": "^12.0.1", "serverless-cloudflare-workers": "^1.2.0", "uuid": "^8.3.2" } From 6a02d528798b76088c4e280ee32eb86c4ab5ca5b Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:17:14 -0400 Subject: [PATCH 04/10] deta - Added data connection natively --- modules/deta-worker/base/index.js | 147 ++++++++++++++++++++++++++++++ modules/deta-worker/base/utils.js | 40 ++++++++ modules/deta-worker/index.js | 19 ++++ 3 files changed, 206 insertions(+) create mode 100644 modules/deta-worker/base/index.js create mode 100644 modules/deta-worker/base/utils.js create mode 100644 modules/deta-worker/index.js diff --git a/modules/deta-worker/base/index.js b/modules/deta-worker/base/index.js new file mode 100644 index 0000000..0ea6448 --- /dev/null +++ b/modules/deta-worker/base/index.js @@ -0,0 +1,147 @@ +import utils, { Action, ACTION_TYPES } from './utils.js' + +function BaseClass(headers, baseURL) { + this.headers = headers + this.baseURL = baseURL +} + +function isObject(v) { + return typeof v === 'object' && v !== null && !Array.isArray(v) +} + +BaseClass.prototype.put = async function(data, key = null) { + if (key != null) { + data.key = key + } + const request = await fetch(`${this.baseURL}/items`, { + method: 'PUT', + headers: this.headers, + body: JSON.stringify({ items: [data] }), + }) + const { + processed: { items: processed = [] } = {}, + failed: { items: failed = [] } = {}, + } = await request.json() + if (Array.isArray(failed) && failed.lengh) { + throw new Error('Failed to put item') + } + return processed[0] +} + +BaseClass.prototype.get = async function(key) { + const request = await fetch(`${this.baseURL}/items/${key}`, { + method: 'GET', + headers: this.headers, + }) + if (!request.ok) { + return null + } + return request.json() +} + +BaseClass.prototype.delete = function(key) { + return fetch(`${this.baseURL}/items/${key}`, { + method: 'DELETE', + headers: this.headers, + }).then(() => null) +} + +BaseClass.prototype.insert = async function(data, key = null) { + if (key != null) { + data.key = key + } + const request = await fetch(`${this.baseURL}/items`, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ item: data }), + }) + const res = await request.json() + if (request.ok) { + return res + } + throw new Error((res.errors || [])[0] || 'Unable to insert item') +} + +BaseClass.prototype.putMany = async function(items) { + if (!Array.isArray(items)) { + throw new Error('Items must be an array') + } + if (items.length > 25) { + throw new Error("We can't put more than 25 items at a time") + } + const _items = items.map((value) => { + if (isObject(value)) { + return value + } + return { value } + }) + const request = await fetch(`${this.baseURL}/items`, { + method: 'PUT', + headers: this.headers, + body: JSON.stringify({ items: _items }), + }) + const res = await request.json() + if (request.ok) { + return res + } + throw new Error((res.errors || [])[0] || 'Unable to put items') +} + +BaseClass.prototype.update = async function(updates, key) { + // build updates payload + const payload = {} + Object.entries(updates).forEach(([field, v]) => { + if (v instanceof Action) { + if (v.action === ACTION_TYPES.Delete) { + payload[v.action] = [ + ...payload[v.action] || [], + field, + ] + } else { + payload[v.action] = { + ...payload[v.action] || {}, + [field]: v.value, + } + } + } else { // implied ACTION_TYPES.Set + payload[ACTION_TYPES.Set] = { + ...payload[ACTION_TYPES.Set] || {}, + [field]: v, + } + } + }) + const req = await fetch(`${this.baseURL}/items/${key}`, { + method: 'PATCH', + headers: this.headers, + body: JSON.stringify(payload), + }) + const res = await req.json() + return req.ok ? null : res +} + +BaseClass.prototype.fetch = async function(query, options) { + const opts = Object.entries(options).reduce((acc, [k, v]) => { + if (v !== undefined) { + acc[k] = k ==='limit' ? parseInt(v, 10) : v + } + return acc + }, {}) + const request = await fetch(`${this.baseURL}/query`, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ + query: Array.isArray(query) ? query : [query], + ...opts, + }), + }) + const { paging, items } = await request.json() + return { + count: paging.size, + last: paging.last, + items, + } +} + +BaseClass.prototype.util = utils + +export default BaseClass diff --git a/modules/deta-worker/base/utils.js b/modules/deta-worker/base/utils.js new file mode 100644 index 0000000..1df706c --- /dev/null +++ b/modules/deta-worker/base/utils.js @@ -0,0 +1,40 @@ +export const ACTION_TYPES = { + Delete: 'delete', // no value + Increment: 'increment', // default 1 + Set: 'set', + Append: 'append', + Prepend: 'prepend', + } + + export function Action({ action, value = null }) { + this.action = action + this.value = value + } + + export default { + delete: function() { + return new Action({ action: ACTION_TYPES.Delete }) + }, + trim: function() { + return this.delete() + }, + increment: function(value = 1) { + return new Action({ action: ACTION_TYPES.Increment, value }) + }, + set: function(value) { + return new Action({ action: ACTION_TYPES.Set, value }) + }, + append: function(value) { + return new Action({ + action: ACTION_TYPES.Append, + value: Array.isArray(value) ? value : [value], + }) + }, + prepend: function(value) { + return new Action({ + ...this.append(value), + action: ACTION_TYPES.Prepend, + }) + }, + } + \ No newline at end of file diff --git a/modules/deta-worker/index.js b/modules/deta-worker/index.js new file mode 100644 index 0000000..8fafae3 --- /dev/null +++ b/modules/deta-worker/index.js @@ -0,0 +1,19 @@ +import BaseClass from './base/index.js' + +function DetaClass(key) { + const k = (key || '').trim() + if (!k) { + throw new Error('Project key is not defined') + } + this.id = k.split('_')[0] + this.headers = { + 'X-API-Key': k, + 'Content-Type': 'application/json', + } +} + +DetaClass.prototype.Base = function(baseName) { + return new BaseClass(this.headers, `https://database.deta.sh/v1/${this.id}/${baseName}`) +} + +export const Deta = (key) => new DetaClass(key) From f064c4228165ac28a964f85ec9916789cdc5274c Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:19:22 -0400 Subject: [PATCH 05/10] sendgrid - Updated sendgrid from email-id --- handlers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/handlers.js b/handlers.js index 4345bd1..b7abde7 100644 --- a/handlers.js +++ b/handlers.js @@ -613,7 +613,7 @@ module.exports.getOTP = async ({ query, url, headers, cf = {} }) => { }, body: JSON.stringify({ personalizations: [{ to: [{ email: u.email }] }], - from: { email: 'dev@ashishjullia.com' }, + from: { email: 'dev@mindswire.com' }, subject: 'Portunus Login OTP/Magic-Link', content: [ { @@ -645,7 +645,7 @@ module.exports.login = async ({ query }) => { return respondError(new HTTPError(`${user} not found`, 404)); } - const isValid = await verifyOTP(otp, otp_secret); + const isValid = await VERIFYOTP(otp, otp_secret); console.log("Valid or not:",isValid); console.log("OTP during login:",otp); console.log("otp secret during login:",otp_secret); @@ -705,7 +705,7 @@ module.exports.getToken = async ({ query }) => { }, body: JSON.stringify({ personalizations: [{ to: [{ email }] }], - from: { email: 'dev@ashishjullia.com' }, + from: { email: 'dev@mindswire.com' }, subject: 'print-env token', content: [{ type: 'text/plain', value: token }], }), From d5e8392bf69b5eae03853dab6c3dfc0bce627d94 Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:21:45 -0400 Subject: [PATCH 06/10] clean - Removed not requried console logging --- handlers.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/handlers.js b/handlers.js index b7abde7..398b33c 100644 --- a/handlers.js +++ b/handlers.js @@ -634,24 +634,17 @@ module.exports.login = async ({ query }) => { try { const { user, otp } = query; if (!user || !otp) { - console.error('User or OTP not supplied'); // I added return respondError(new HTTPError('User or OTP not supplied', 400)); } const { email, jwt_uuid, otp_secret } = (await fetchUser(user)) || { teams: [], }; if (!email || !otp_secret) { - console.error(`${user} not found`); // I added return respondError(new HTTPError(`${user} not found`, 404)); } const isValid = await VERIFYOTP(otp, otp_secret); - console.log("Valid or not:",isValid); - console.log("OTP during login:",otp); - console.log("otp secret during login:",otp_secret); - console.log("user values during login:", await fetchUser(user)); if (!isValid) { - console.error('Invalid OTP'); // I added return respondError(new HTTPError('Invalid OTP', 403)); } @@ -659,11 +652,9 @@ module.exports.login = async ({ query }) => { const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET); return respondJSON({ payload: { jwt: token } }); } catch (err) { - console.error('Error generating JWT:', err); // I added return respondError(new HTTPError('JWT token generation failed', 500)); } } catch (err) { - console.error('Error in login function:', err); // I added return respondError(new HTTPError('Internal server error', 500)); } }; From 5e434e32e61116fd33dbccdf1ec3102920869101 Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Sat, 15 Jul 2023 09:29:56 -0400 Subject: [PATCH 07/10] package-lock - Updated --- package-lock.json | 363 ++-------------------------------------------- 1 file changed, 11 insertions(+), 352 deletions(-) diff --git a/package-lock.json b/package-lock.json index a78cead..61ed522 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "deta-worker": "^0.2.0-alpha", + "@tsndr/cloudflare-worker-jwt": "^2.2.1", "eslint-plugin-jest": "^25.3.4", "itty-router": "^2.5.2", "itty-router-extras": "^0.4.2", - "jsonwebtoken": "^8.5.1", - "otplib": "^12.0.1", "serverless-cloudflare-workers": "^1.2.0", "uuid": "^8.3.2" }, @@ -554,17 +552,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", @@ -1268,48 +1255,6 @@ "node": ">= 8" } }, - "node_modules/@otplib/core": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", - "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==" - }, - "node_modules/@otplib/plugin-crypto": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", - "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", - "dependencies": { - "@otplib/core": "^12.0.1" - } - }, - "node_modules/@otplib/plugin-thirty-two": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", - "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", - "dependencies": { - "@otplib/core": "^12.0.1", - "thirty-two": "^1.0.2" - } - }, - "node_modules/@otplib/preset-default": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", - "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", - "dependencies": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" - } - }, - "node_modules/@otplib/preset-v11": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", - "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", - "dependencies": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" - } - }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -1337,6 +1282,11 @@ "node": ">= 6" } }, + "node_modules/@tsndr/cloudflare-worker-jwt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@tsndr/cloudflare-worker-jwt/-/cloudflare-worker-jwt-2.2.1.tgz", + "integrity": "sha512-yYxjmag5AXXs7FDCUlse8cCTQlug3fbrqUb4/s4kvdauOg5wq2V6BayHD/OUWOGmgohefFRqu4vA5phU3+Cyng==" + }, "node_modules/@types/babel__core": { "version": "7.1.18", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", @@ -2421,11 +2371,6 @@ "isarray": "^1.0.0" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3016,14 +2961,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/deta-worker": { - "version": "0.2.0-alpha.1", - "resolved": "https://registry.npmjs.org/deta-worker/-/deta-worker-0.2.0-alpha.1.tgz", - "integrity": "sha512-dERRUFIQGmwjC+sv8Fzl6X9jB+kE+EbxtFkaQiBWSsCRqoUnSxFeOCFdH96VwqUDg2NZdj0g1xNEihyt9cp+jw==", - "dependencies": { - "@babel/runtime": "^7.15.3" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3120,14 +3057,6 @@ "stream-shift": "^1.0.0" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.68", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz", @@ -5484,54 +5413,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -5629,46 +5510,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6256,16 +6102,6 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, - "node_modules/otplib": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", - "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", - "dependencies": { - "@otplib/core": "^12.0.1", - "@otplib/preset-default": "^12.0.1", - "@otplib/preset-v11": "^12.0.1" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -6736,11 +6572,6 @@ "node": ">=8.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, "node_modules/regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -7598,14 +7429,6 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, - "node_modules/thirty-two": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", - "integrity": "sha1-TKL//AKlEpDSdEueP1V2k8prYno=", - "engines": { - "node": ">=0.2.6" - } - }, "node_modules/throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -9297,14 +9120,6 @@ "@babel/helper-plugin-utils": "^7.16.7" } }, - "@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", @@ -9863,48 +9678,6 @@ "fastq": "^1.6.0" } }, - "@otplib/core": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", - "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==" - }, - "@otplib/plugin-crypto": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", - "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", - "requires": { - "@otplib/core": "^12.0.1" - } - }, - "@otplib/plugin-thirty-two": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", - "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", - "requires": { - "@otplib/core": "^12.0.1", - "thirty-two": "^1.0.2" - } - }, - "@otplib/preset-default": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", - "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", - "requires": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" - } - }, - "@otplib/preset-v11": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", - "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", - "requires": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" - } - }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -9929,6 +9702,11 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@tsndr/cloudflare-worker-jwt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@tsndr/cloudflare-worker-jwt/-/cloudflare-worker-jwt-2.2.1.tgz", + "integrity": "sha512-yYxjmag5AXXs7FDCUlse8cCTQlug3fbrqUb4/s4kvdauOg5wq2V6BayHD/OUWOGmgohefFRqu4vA5phU3+Cyng==" + }, "@types/babel__core": { "version": "7.1.18", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", @@ -10803,11 +10581,6 @@ "isarray": "^1.0.0" } }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -11308,14 +11081,6 @@ "minimalistic-assert": "^1.0.0" } }, - "deta-worker": { - "version": "0.2.0-alpha.1", - "resolved": "https://registry.npmjs.org/deta-worker/-/deta-worker-0.2.0-alpha.1.tgz", - "integrity": "sha512-dERRUFIQGmwjC+sv8Fzl6X9jB+kE+EbxtFkaQiBWSsCRqoUnSxFeOCFdH96VwqUDg2NZdj0g1xNEihyt9cp+jw==", - "requires": { - "@babel/runtime": "^7.15.3" - } - }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -11394,14 +11159,6 @@ "stream-shift": "^1.0.0" } }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "electron-to-chromium": { "version": "1.4.68", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz", @@ -13194,49 +12951,6 @@ "graceful-fs": "^4.1.6" } }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -13312,46 +13026,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -13836,16 +13515,6 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, - "otplib": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", - "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", - "requires": { - "@otplib/core": "^12.0.1", - "@otplib/preset-default": "^12.0.1", - "@otplib/preset-v11": "^12.0.1" - } - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -14213,11 +13882,6 @@ "picomatch": "^2.2.1" } }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -14870,11 +14534,6 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, - "thirty-two": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", - "integrity": "sha1-TKL//AKlEpDSdEueP1V2k8prYno=" - }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", From ac84b9085161c32581cd9298de9073beeb677a50 Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Mon, 17 Jul 2023 14:57:09 -0400 Subject: [PATCH 08/10] importpath - Updated to native ref deta db --- modules/db.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/db.js b/modules/db.js index e955bbc..3bc28be 100644 --- a/modules/db.js +++ b/modules/db.js @@ -1,3 +1,3 @@ -const { Deta } = require('deta-worker') +const { Deta } = require('./deta-worker/index.js') module.exports = Deta(DETA_KEY) From 836156fbc9d8f8d833af909df8d54cfba3df1d9b Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Tue, 18 Jul 2023 17:32:01 -0400 Subject: [PATCH 09/10] formatting - Updated --- handlers.js | 49 ++++++++-------- modules/auth.js | 12 ++-- modules/deta-worker/base/index.js | 28 ++++----- modules/deta-worker/base/utils.js | 77 ++++++++++++------------ modules/deta-worker/index.js | 7 ++- modules/otp.js | 98 +++++++++++++++---------------- 6 files changed, 136 insertions(+), 135 deletions(-) diff --git a/handlers.js b/handlers.js index 398b33c..34107fe 100644 --- a/handlers.js +++ b/handlers.js @@ -1,5 +1,5 @@ -import jwt from '@tsndr/cloudflare-worker-jwt' -const { generateOTP, verifyOTP } = require('./modules/otp.js'); +const jwt = require('@tsndr/cloudflare-worker-jwt') +const { generateOTP, verifyOTP } = require('./modules/otp.js') const { v4: uuidv4 } = require('uuid') @@ -489,7 +489,6 @@ module.exports.addUserToAdmin = async ({ } await addUserToAdmin({ team, user: kvUser }) - return respondJSON({ payload: { key: kvUser.key } }) } catch (err) { @@ -589,10 +588,10 @@ module.exports.getOTP = async ({ query, url, headers, cf = {} }) => { u.updated = new Date() await updateUser(u) } - let otp; - otp = await generateOTP(u.otp_secret); - const timeRemaining = 30 - (Math.floor(Date.now() / 1000) % 30); - const expiresAt = new Date(Date.now() + timeRemaining * 1000); + let otp + otp = await generateOTP(u.otp_secret) + const timeRemaining = 30 - (Math.floor(Date.now() / 1000) % 30) + const expiresAt = new Date(Date.now() + timeRemaining * 1000) const { origin: _origin } = new URL(url) const defaultOrigin = `${_origin}/login` const locale = (headers.get('Accept-Language') || '').split(',')[0] || 'en' @@ -623,41 +622,43 @@ module.exports.getOTP = async ({ query, url, headers, cf = {} }) => { ], }), }) - return respondJSON({ payload: { message: `OTP/Magic-Link sent to ${user}` } }) + return respondJSON({ + payload: { message: `OTP/Magic-Link sent to ${user}` }, + }) } catch (error) { - console.error("Error in getOTP: ", error); - throw error; + console.error('Error in getOTP: ', error) + throw error } } module.exports.login = async ({ query }) => { try { - const { user, otp } = query; + const { user, otp } = query if (!user || !otp) { - return respondError(new HTTPError('User or OTP not supplied', 400)); + return respondError(new HTTPError('User or OTP not supplied', 400)) } const { email, jwt_uuid, otp_secret } = (await fetchUser(user)) || { teams: [], - }; + } if (!email || !otp_secret) { - return respondError(new HTTPError(`${user} not found`, 404)); + return respondError(new HTTPError(`${user} not found`, 404)) } - - const isValid = await VERIFYOTP(otp, otp_secret); + + const isValid = await VERIFYOTP(otp, otp_secret) if (!isValid) { - return respondError(new HTTPError('Invalid OTP', 403)); + return respondError(new HTTPError('Invalid OTP', 403)) } - + try { - const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET); - return respondJSON({ payload: { jwt: token } }); + const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) + return respondJSON({ payload: { jwt: token } }) } catch (err) { - return respondError(new HTTPError('JWT token generation failed', 500)); + return respondError(new HTTPError('JWT token generation failed', 500)) } } catch (err) { - return respondError(new HTTPError('Internal server error', 500)); + return respondError(new HTTPError('Internal server error', 500)) } -}; +} // legacy direct JWT through email module.exports.getToken = async ({ query }) => { @@ -673,7 +674,7 @@ module.exports.getToken = async ({ query }) => { // const token = jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) // const token = await new jose.SignJWT({ email, jwt_uuid }).sign(TOKEN_SECRET) const token = await jwt.sign({ email, jwt_uuid }, TOKEN_SECRET) - + // const sharedSecret = Uint8Array.from(TOKEN_SECRET, c => c.charCodeAt(0)) // // Import your TOKEN_SECRET as a key // const key = await importKey( diff --git a/modules/auth.js b/modules/auth.js index d2a5072..937d782 100644 --- a/modules/auth.js +++ b/modules/auth.js @@ -6,7 +6,7 @@ const { getKVUser } = require('./users') const verifyJWT = async (headers) => { const token = headers.get('portunus-jwt') if (!token) { - console.error("Error: No portunus-jwt found in headers.") + console.error('Error: No portunus-jwt found in headers.') throw new HTTPError('Auth requires portunus-jwt', 403) } let access = {} @@ -17,11 +17,11 @@ const verifyJWT = async (headers) => { const { payload } = jwt.decode(token) access = payload } catch (error) { - console.error("Error: Invalid portunus-jwt.", error) + console.error('Error: Invalid portunus-jwt.', error) throw new HTTPError('Invalid portunus-jwt', 403) } if (!access.email) { - console.error("Error: No email in portunus-jwt.") + console.error('Error: No email in portunus-jwt.') throw new HTTPError('Invalid portunus-jwt: no email', 403) } return access @@ -30,11 +30,11 @@ const verifyJWT = async (headers) => { const verifyUser = async (access) => { const user = await getKVUser(access.email) if (!user) { - console.error("Error: No user found with this email.") + console.error('Error: No user found with this email.') throw new HTTPError('No user found with this email', 404) } if (user.jwt_uuid !== access.jwt_uuid) { - console.error("Error: UUID mismatch in portunus-jwt.") + console.error('Error: UUID mismatch in portunus-jwt.') throw new HTTPError('Invalid portunus-jwt: UUID mismatch', 403) } @@ -47,7 +47,7 @@ module.exports.withRequireUser = async (req) => { const access = await verifyJWT(headers) req.user = await verifyUser(access) } catch (err) { - console.error("Error: Failed to verify user.", err) + console.error('Error: Failed to verify user.', err) return respondError(req) } } diff --git a/modules/deta-worker/base/index.js b/modules/deta-worker/base/index.js index 0ea6448..524adbd 100644 --- a/modules/deta-worker/base/index.js +++ b/modules/deta-worker/base/index.js @@ -9,7 +9,7 @@ function isObject(v) { return typeof v === 'object' && v !== null && !Array.isArray(v) } -BaseClass.prototype.put = async function(data, key = null) { +BaseClass.prototype.put = async function (data, key = null) { if (key != null) { data.key = key } @@ -28,7 +28,7 @@ BaseClass.prototype.put = async function(data, key = null) { return processed[0] } -BaseClass.prototype.get = async function(key) { +BaseClass.prototype.get = async function (key) { const request = await fetch(`${this.baseURL}/items/${key}`, { method: 'GET', headers: this.headers, @@ -39,14 +39,14 @@ BaseClass.prototype.get = async function(key) { return request.json() } -BaseClass.prototype.delete = function(key) { +BaseClass.prototype.delete = function (key) { return fetch(`${this.baseURL}/items/${key}`, { method: 'DELETE', headers: this.headers, }).then(() => null) } -BaseClass.prototype.insert = async function(data, key = null) { +BaseClass.prototype.insert = async function (data, key = null) { if (key != null) { data.key = key } @@ -62,7 +62,7 @@ BaseClass.prototype.insert = async function(data, key = null) { throw new Error((res.errors || [])[0] || 'Unable to insert item') } -BaseClass.prototype.putMany = async function(items) { +BaseClass.prototype.putMany = async function (items) { if (!Array.isArray(items)) { throw new Error('Items must be an array') } @@ -87,25 +87,23 @@ BaseClass.prototype.putMany = async function(items) { throw new Error((res.errors || [])[0] || 'Unable to put items') } -BaseClass.prototype.update = async function(updates, key) { +BaseClass.prototype.update = async function (updates, key) { // build updates payload const payload = {} Object.entries(updates).forEach(([field, v]) => { if (v instanceof Action) { if (v.action === ACTION_TYPES.Delete) { - payload[v.action] = [ - ...payload[v.action] || [], - field, - ] + payload[v.action] = [...(payload[v.action] || []), field] } else { payload[v.action] = { - ...payload[v.action] || {}, + ...(payload[v.action] || {}), [field]: v.value, } } - } else { // implied ACTION_TYPES.Set + } else { + // implied ACTION_TYPES.Set payload[ACTION_TYPES.Set] = { - ...payload[ACTION_TYPES.Set] || {}, + ...(payload[ACTION_TYPES.Set] || {}), [field]: v, } } @@ -119,10 +117,10 @@ BaseClass.prototype.update = async function(updates, key) { return req.ok ? null : res } -BaseClass.prototype.fetch = async function(query, options) { +BaseClass.prototype.fetch = async function (query, options) { const opts = Object.entries(options).reduce((acc, [k, v]) => { if (v !== undefined) { - acc[k] = k ==='limit' ? parseInt(v, 10) : v + acc[k] = k === 'limit' ? parseInt(v, 10) : v } return acc }, {}) diff --git a/modules/deta-worker/base/utils.js b/modules/deta-worker/base/utils.js index 1df706c..044cf17 100644 --- a/modules/deta-worker/base/utils.js +++ b/modules/deta-worker/base/utils.js @@ -1,40 +1,39 @@ export const ACTION_TYPES = { - Delete: 'delete', // no value - Increment: 'increment', // default 1 - Set: 'set', - Append: 'append', - Prepend: 'prepend', - } - - export function Action({ action, value = null }) { - this.action = action - this.value = value - } - - export default { - delete: function() { - return new Action({ action: ACTION_TYPES.Delete }) - }, - trim: function() { - return this.delete() - }, - increment: function(value = 1) { - return new Action({ action: ACTION_TYPES.Increment, value }) - }, - set: function(value) { - return new Action({ action: ACTION_TYPES.Set, value }) - }, - append: function(value) { - return new Action({ - action: ACTION_TYPES.Append, - value: Array.isArray(value) ? value : [value], - }) - }, - prepend: function(value) { - return new Action({ - ...this.append(value), - action: ACTION_TYPES.Prepend, - }) - }, - } - \ No newline at end of file + Delete: 'delete', // no value + Increment: 'increment', // default 1 + Set: 'set', + Append: 'append', + Prepend: 'prepend', +} + +export function Action({ action, value = null }) { + this.action = action + this.value = value +} + +export default { + delete: function () { + return new Action({ action: ACTION_TYPES.Delete }) + }, + trim: function () { + return this.delete() + }, + increment: function (value = 1) { + return new Action({ action: ACTION_TYPES.Increment, value }) + }, + set: function (value) { + return new Action({ action: ACTION_TYPES.Set, value }) + }, + append: function (value) { + return new Action({ + action: ACTION_TYPES.Append, + value: Array.isArray(value) ? value : [value], + }) + }, + prepend: function (value) { + return new Action({ + ...this.append(value), + action: ACTION_TYPES.Prepend, + }) + }, +} diff --git a/modules/deta-worker/index.js b/modules/deta-worker/index.js index 8fafae3..232b7f5 100644 --- a/modules/deta-worker/index.js +++ b/modules/deta-worker/index.js @@ -12,8 +12,11 @@ function DetaClass(key) { } } -DetaClass.prototype.Base = function(baseName) { - return new BaseClass(this.headers, `https://database.deta.sh/v1/${this.id}/${baseName}`) +DetaClass.prototype.Base = function (baseName) { + return new BaseClass( + this.headers, + `https://database.deta.sh/v1/${this.id}/${baseName}` + ) } export const Deta = (key) => new DetaClass(key) diff --git a/modules/otp.js b/modules/otp.js index ddaef75..f447e36 100644 --- a/modules/otp.js +++ b/modules/otp.js @@ -1,51 +1,51 @@ async function generateOTP(secret, timePeriodOffset = 0) { - // Get the current time - const time = Math.floor(Date.now() / 1000) - - // Calculate the time period with the offset - const timePeriod = Math.floor(time / 30) + timePeriodOffset; - - const timeBuffer = new ArrayBuffer(8); - (new DataView(timeBuffer)).setBigUint64(0, BigInt(timePeriod), false); - - - // Create a key from the secret - const key = await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(secret), - { name: 'HMAC', hash: 'SHA-1' }, - false, - ['sign'] - ) - - // Sign the time buffer with the key - const signatureBuffer = await crypto.subtle.sign('HMAC', key, timeBuffer) - const signature = new Uint8Array(signatureBuffer) - - // Take the last 4 bits of the signature - const offset = signature[signature.length - 1] & 0xf - - // Take 4 bytes from the signature starting at the offset - const otp = (signature[offset] & 0x7f) << 24 | - (signature[offset + 1] & 0xff) << 16 | - (signature[offset + 2] & 0xff) << 8 | - (signature[offset + 3] & 0xff) - - // Take the last 6 digits of the otp - const otpStr = (otp % 1000000).toString().padStart(6, '0') - - return otpStr - } - - async function verifyOTP(otp, secret) { - // Check the OTP for the current time period and the previous and next time periods - const otps = [ - await generateOTP(secret, -1), - await generateOTP(secret, 0), - await generateOTP(secret, 1) - ]; - - return otps.includes(otp); + // Get the current time + const time = Math.floor(Date.now() / 1000) + + // Calculate the time period with the offset + const timePeriod = Math.floor(time / 30) + timePeriodOffset + + const timeBuffer = new ArrayBuffer(8) + new DataView(timeBuffer).setBigUint64(0, BigInt(timePeriod), false) + + // Create a key from the secret + const key = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(secret), + { name: 'HMAC', hash: 'SHA-1' }, + false, + ['sign'] + ) + + // Sign the time buffer with the key + const signatureBuffer = await crypto.subtle.sign('HMAC', key, timeBuffer) + const signature = new Uint8Array(signatureBuffer) + + // Take the last 4 bits of the signature + const offset = signature[signature.length - 1] & 0xf + + // Take 4 bytes from the signature starting at the offset + const otp = + ((signature[offset] & 0x7f) << 24) | + ((signature[offset + 1] & 0xff) << 16) | + ((signature[offset + 2] & 0xff) << 8) | + (signature[offset + 3] & 0xff) + + // Take the last 6 digits of the otp + const otpStr = (otp % 1000000).toString().padStart(6, '0') + + return otpStr +} + +async function verifyOTP(otp, secret) { + // Check the OTP for the current time period and the previous and next time periods + const otps = [ + await generateOTP(secret, -1), + await generateOTP(secret, 0), + await generateOTP(secret, 1), + ] + + return otps.includes(otp) } - -module.exports = { generateOTP, verifyOTP }; + +module.exports = { generateOTP, verifyOTP } From e5fd7e04691d68abd41f2bd8749c081ae5926c4a Mon Sep 17 00:00:00 2001 From: ashishjullia19 Date: Tue, 18 Jul 2023 17:48:05 -0400 Subject: [PATCH 10/10] formatting - Fixed VERIFYOTP function name and refs to verifyOTP --- handlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers.js b/handlers.js index 34107fe..f3dee96 100644 --- a/handlers.js +++ b/handlers.js @@ -644,7 +644,7 @@ module.exports.login = async ({ query }) => { return respondError(new HTTPError(`${user} not found`, 404)) } - const isValid = await VERIFYOTP(otp, otp_secret) + const isValid = await verifyOTP(otp, otp_secret) if (!isValid) { return respondError(new HTTPError('Invalid OTP', 403)) }