UNPKG

blade

Version:
1,062 lines (1,058 loc) • 35.6 kB
import { a as getSessionCookie, i as generateUniqueId, n as EMAIL_VERIFICATION_COOLDOWN, r as avoidEmptyFields, t as AUTH_SECRET } from "../../../utils-CUmNvaMX-CxXAjaLV.js"; import { triggers } from "blade/schema"; import { EmptyFieldsError, InvalidFieldsError, MultipleWithInstructionsError, RecordNotFoundError, SetNotAllowedError, TooManyRequestsError } from "blade/errors"; import { getRecordIdentifier, signJWT } from "blade/server/utils"; //#region ../blade-auth/dist/crypto-o_nOQQQv.js /** * Utilities for hex, bytes, CSPRNG. * @module */ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */ function isBytes(a) { return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array"; } /** Asserts something is positive integer. */ function anumber(n, title = "") { if (!Number.isSafeInteger(n) || n < 0) { const prefix = title && `"${title}" `; throw new Error(`${prefix}expected integer >= 0, got ${n}`); } } /** Asserts something is Uint8Array. */ function abytes(value, length, title = "") { const bytes = isBytes(value); const len = value?.length; const needsLen = length !== void 0; if (!bytes || needsLen && len !== length) { const prefix = title && `"${title}" `; const ofLen = needsLen ? ` of length ${length}` : ""; const got = bytes ? `length=${len}` : `type=${typeof value}`; throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got); } return value; } /** Asserts something is hash */ function ahash(h) { if (typeof h !== "function" || typeof h.create !== "function") throw new Error("Hash must wrapped by utils.createHasher"); anumber(h.outputLen); anumber(h.blockLen); } /** Asserts a hash instance has not been destroyed / finished */ function aexists(instance, checkFinished = true) { if (instance.destroyed) throw new Error("Hash instance has been destroyed"); if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called"); } /** Asserts output is properly-sized byte array */ function aoutput(out, instance) { abytes(out, void 0, "digestInto() output"); const min = instance.outputLen; if (out.length < min) throw new Error("\"digestInto() output\" expected to be of length >=" + min); } /** Cast u8 / u16 / u32 to u32. */ function u32(arr) { return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4)); } /** Zeroize a byte array. Warning: JS provides no guarantees. */ function clean(...arrays) { for (let i = 0; i < arrays.length; i++) arrays[i].fill(0); } /** Create DataView of an array for easy byte-level manipulation. */ function createView(arr) { return new DataView(arr.buffer, arr.byteOffset, arr.byteLength); } /** The rotate right (circular right shift) operation for uint32 */ function rotr(word, shift) { return word << 32 - shift | word >>> shift; } /** The rotate left (circular left shift) operation for uint32 */ function rotl(word, shift) { return word << shift | word >>> 32 - shift >>> 0; } /** Is current platform little-endian? Most are. Big-Endian platform: IBM */ const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)(); /** The byte swap operation for uint32 */ function byteSwap(word) { return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255; } /** In place byte swap for Uint32Array */ function byteSwap32(arr) { for (let i = 0; i < arr.length; i++) arr[i] = byteSwap(arr[i]); return arr; } const swap32IfBE = isLE ? (u) => u : byteSwap32; const hasHexBuiltin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function")(); const asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; function asciiToBase16(ch) { if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); } /** * Convert hex string to byte array. Uses built-in function, when available. * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23]) */ function hexToBytes(hex$1) { if (typeof hex$1 !== "string") throw new Error("hex string expected, got " + typeof hex$1); if (hasHexBuiltin) return Uint8Array.fromHex(hex$1); const hl = hex$1.length; const al = hl / 2; if (hl % 2) throw new Error("hex string expected, got unpadded hex of length " + hl); const array = new Uint8Array(al); for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { const n1 = asciiToBase16(hex$1.charCodeAt(hi)); const n2 = asciiToBase16(hex$1.charCodeAt(hi + 1)); if (n1 === void 0 || n2 === void 0) { const char = hex$1[hi] + hex$1[hi + 1]; throw new Error("hex string expected, got non-hex character \"" + char + "\" at index " + hi); } array[ai] = n1 * 16 + n2; } return array; } /** * There is no setImmediate in browser and setTimeout is slow. * Call of async fn will return Promise, which will be fullfiled only on * next scheduler queue processing step and this is exactly what we need. */ const nextTick = async () => {}; /** Returns control to thread each 'tick' ms to avoid blocking. */ async function asyncLoop(iters, tick, cb) { let ts = Date.now(); for (let i = 0; i < iters; i++) { cb(i); const diff = Date.now() - ts; if (diff >= 0 && diff < tick) continue; await nextTick(); ts += diff; } } /** * Converts string to bytes using UTF8 encoding. * Built-in doesn't validate input to be string: we do the check. * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99]) */ function utf8ToBytes(str) { if (typeof str !== "string") throw new Error("string expected"); return new Uint8Array(new TextEncoder().encode(str)); } /** * Helper for KDFs: consumes uint8array or string. * When string is passed, does utf8 decoding, using TextDecoder. */ function kdfInputToBytes(data, errorTitle = "") { if (typeof data === "string") return utf8ToBytes(data); return abytes(data, void 0, errorTitle); } /** Merges default options and passed options. */ function checkOpts(defaults, opts) { if (opts !== void 0 && {}.toString.call(opts) !== "[object Object]") throw new Error("options must be object or undefined"); return Object.assign(defaults, opts); } /** Creates function with outputLen, blockLen, create properties from a class constructor. */ function createHasher(hashCons, info = {}) { const hashC = (msg, opts) => hashCons(opts).update(msg).digest(); const tmp = hashCons(void 0); hashC.outputLen = tmp.outputLen; hashC.blockLen = tmp.blockLen; hashC.create = (opts) => hashCons(opts); Object.assign(hashC, info); return Object.freeze(hashC); } /** Creates OID opts for NIST hashes, with prefix 06 09 60 86 48 01 65 03 04 02. */ const oidNist = (suffix) => ({ oid: Uint8Array.from([ 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix ]) }); /** Internal class for HMAC. */ var _HMAC = class { oHash; iHash; blockLen; outputLen; finished = false; destroyed = false; constructor(hash, key) { ahash(hash); abytes(key, void 0, "key"); this.iHash = hash.create(); if (typeof this.iHash.update !== "function") throw new Error("Expected instance of class which extends utils.Hash"); this.blockLen = this.iHash.blockLen; this.outputLen = this.iHash.outputLen; const blockLen = this.blockLen; const pad = new Uint8Array(blockLen); pad.set(key.length > blockLen ? hash.create().update(key).digest() : key); for (let i = 0; i < pad.length; i++) pad[i] ^= 54; this.iHash.update(pad); this.oHash = hash.create(); for (let i = 0; i < pad.length; i++) pad[i] ^= 106; this.oHash.update(pad); clean(pad); } update(buf) { aexists(this); this.iHash.update(buf); return this; } digestInto(out) { aexists(this); abytes(out, this.outputLen, "output"); this.finished = true; this.iHash.digestInto(out); this.oHash.update(out); this.oHash.digestInto(out); this.destroy(); } digest() { const out = new Uint8Array(this.oHash.outputLen); this.digestInto(out); return out; } _cloneInto(to) { to ||= Object.create(Object.getPrototypeOf(this), {}); const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; to = to; to.finished = finished; to.destroyed = destroyed; to.blockLen = blockLen; to.outputLen = outputLen; to.oHash = oHash._cloneInto(to.oHash); to.iHash = iHash._cloneInto(to.iHash); return to; } clone() { return this._cloneInto(); } destroy() { this.destroyed = true; this.oHash.destroy(); this.iHash.destroy(); } }; /** * HMAC: RFC2104 message authentication code. * @param hash - function that would be used e.g. sha256 * @param key - message key * @param message - message data * @example * import { hmac } from '@noble/hashes/hmac'; * import { sha256 } from '@noble/hashes/sha2'; * const mac1 = hmac(sha256, 'key', 'message'); */ const hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest(); hmac.create = (hash, key) => new _HMAC(hash, key); function pbkdf2Init(hash, _password, _salt, _opts) { ahash(hash); const { c, dkLen, asyncTick } = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts); anumber(c, "c"); anumber(dkLen, "dkLen"); anumber(asyncTick, "asyncTick"); if (c < 1) throw new Error("iterations (c) must be >= 1"); const password = kdfInputToBytes(_password, "password"); const salt = kdfInputToBytes(_salt, "salt"); const DK = new Uint8Array(dkLen); const PRF = hmac.create(hash, password); return { c, dkLen, asyncTick, DK, PRF, PRFSalt: PRF._cloneInto().update(salt) }; } function pbkdf2Output(PRF, PRFSalt, DK, prfW, u) { PRF.destroy(); PRFSalt.destroy(); if (prfW) prfW.destroy(); clean(u); return DK; } /** * PBKDF2-HMAC: RFC 2898 key derivation function * @param hash - hash function that would be used e.g. sha256 * @param password - password from which a derived key is generated * @param salt - cryptographic salt * @param opts - {c, dkLen} where c is work factor and dkLen is output message size * @example * const key = pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) }); */ function pbkdf2(hash, password, salt, opts) { const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts); let prfW; const arr = new Uint8Array(4); const view = createView(arr); const u = new Uint8Array(PRF.outputLen); for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) { const Ti = DK.subarray(pos, pos + PRF.outputLen); view.setInt32(0, ti, false); (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u); Ti.set(u.subarray(0, Ti.length)); for (let ui = 1; ui < c; ui++) { PRF._cloneInto(prfW).update(u).digestInto(u); for (let i = 0; i < Ti.length; i++) Ti[i] ^= u[i]; } } return pbkdf2Output(PRF, PRFSalt, DK, prfW, u); } /** Choice: a ? b : c */ function Chi(a, b, c) { return a & b ^ ~a & c; } /** Majority function, true if any two inputs is true. */ function Maj(a, b, c) { return a & b ^ a & c ^ b & c; } /** * Merkle-Damgard hash construction base class. * Could be used to create MD5, RIPEMD, SHA1, SHA2. */ var HashMD = class { blockLen; outputLen; padOffset; isLE; buffer; view; finished = false; length = 0; pos = 0; destroyed = false; constructor(blockLen, outputLen, padOffset, isLE$1) { this.blockLen = blockLen; this.outputLen = outputLen; this.padOffset = padOffset; this.isLE = isLE$1; this.buffer = new Uint8Array(blockLen); this.view = createView(this.buffer); } update(data) { aexists(this); abytes(data); const { view, buffer, blockLen } = this; const len = data.length; for (let pos = 0; pos < len;) { const take = Math.min(blockLen - this.pos, len - pos); if (take === blockLen) { const dataView = createView(data); for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos); continue; } buffer.set(data.subarray(pos, pos + take), this.pos); this.pos += take; pos += take; if (this.pos === blockLen) { this.process(view, 0); this.pos = 0; } } this.length += data.length; this.roundClean(); return this; } digestInto(out) { aexists(this); aoutput(out, this); this.finished = true; const { buffer, view, blockLen, isLE: isLE$1 } = this; let { pos } = this; buffer[pos++] = 128; clean(this.buffer.subarray(pos)); if (this.padOffset > blockLen - pos) { this.process(view, 0); pos = 0; } for (let i = pos; i < blockLen; i++) buffer[i] = 0; view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE$1); this.process(view, 0); const oview = createView(out); const len = this.outputLen; if (len % 4) throw new Error("_sha2: outputLen must be aligned to 32bit"); const outLen = len / 4; const state = this.get(); if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state"); for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE$1); } digest() { const { buffer, outputLen } = this; this.digestInto(buffer); const res = buffer.slice(0, outputLen); this.destroy(); return res; } _cloneInto(to) { to ||= new this.constructor(); to.set(...this.get()); const { blockLen, buffer, length, finished, destroyed, pos } = this; to.destroyed = destroyed; to.finished = finished; to.length = length; to.pos = pos; if (length % blockLen) to.buffer.set(buffer); return to; } clone() { return this._cloneInto(); } }; /** * Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53. * Check out `test/misc/sha2-gen-iv.js` for recomputation guide. */ /** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */ const SHA256_IV = /* @__PURE__ */ Uint32Array.from([ 1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225 ]); /** * Round constants: * First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311) */ const SHA256_K = /* @__PURE__ */ Uint32Array.from([ 1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298 ]); /** Reusable temporary buffer. "W" comes straight from spec. */ const SHA256_W = /* @__PURE__ */ new Uint32Array(64); /** Internal 32-byte base SHA2 hash class. */ var SHA2_32B = class extends HashMD { constructor(outputLen) { super(64, outputLen, 8, false); } get() { const { A, B, C, D, E, F, G, H } = this; return [ A, B, C, D, E, F, G, H ]; } set(A, B, C, D, E, F, G, H) { this.A = A | 0; this.B = B | 0; this.C = C | 0; this.D = D | 0; this.E = E | 0; this.F = F | 0; this.G = G | 0; this.H = H | 0; } process(view, offset) { for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false); for (let i = 16; i < 64; i++) { const W15 = SHA256_W[i - 15]; const W2 = SHA256_W[i - 2]; const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3; SHA256_W[i] = (rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10) + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0; } let { A, B, C, D, E, F, G, H } = this; for (let i = 0; i < 64; i++) { const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0; const T2 = (rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22)) + Maj(A, B, C) | 0; H = G; G = F; F = E; E = D + T1 | 0; D = C; C = B; B = A; A = T1 + T2 | 0; } A = A + this.A | 0; B = B + this.B | 0; C = C + this.C | 0; D = D + this.D | 0; E = E + this.E | 0; F = F + this.F | 0; G = G + this.G | 0; H = H + this.H | 0; this.set(A, B, C, D, E, F, G, H); } roundClean() { clean(SHA256_W); } destroy() { this.set(0, 0, 0, 0, 0, 0, 0, 0); clean(this.buffer); } }; /** Internal SHA2-256 hash class. */ var _SHA256 = class extends SHA2_32B { A = SHA256_IV[0] | 0; B = SHA256_IV[1] | 0; C = SHA256_IV[2] | 0; D = SHA256_IV[3] | 0; E = SHA256_IV[4] | 0; F = SHA256_IV[5] | 0; G = SHA256_IV[6] | 0; H = SHA256_IV[7] | 0; constructor() { super(32); } }; /** * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info: * * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack. * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025. * - Each sha256 hash is executing 2^18 bit operations. * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule. */ const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(), /* @__PURE__ */ oidNist(1)); function XorAndSalsa(prev, pi, input, ii, out, oi) { let y00 = prev[pi++] ^ input[ii++], y01 = prev[pi++] ^ input[ii++]; let y02 = prev[pi++] ^ input[ii++], y03 = prev[pi++] ^ input[ii++]; let y04 = prev[pi++] ^ input[ii++], y05 = prev[pi++] ^ input[ii++]; let y06 = prev[pi++] ^ input[ii++], y07 = prev[pi++] ^ input[ii++]; let y08 = prev[pi++] ^ input[ii++], y09 = prev[pi++] ^ input[ii++]; let y10 = prev[pi++] ^ input[ii++], y11 = prev[pi++] ^ input[ii++]; let y12 = prev[pi++] ^ input[ii++], y13 = prev[pi++] ^ input[ii++]; let y14 = prev[pi++] ^ input[ii++], y15 = prev[pi++] ^ input[ii++]; let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15; for (let i = 0; i < 8; i += 2) { x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9); x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18); x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9); x01 ^= rotl(x13 + x09 | 0, 13); x05 ^= rotl(x01 + x13 | 0, 18); x14 ^= rotl(x10 + x06 | 0, 7); x02 ^= rotl(x14 + x10 | 0, 9); x06 ^= rotl(x02 + x14 | 0, 13); x10 ^= rotl(x06 + x02 | 0, 18); x03 ^= rotl(x15 + x11 | 0, 7); x07 ^= rotl(x03 + x15 | 0, 9); x11 ^= rotl(x07 + x03 | 0, 13); x15 ^= rotl(x11 + x07 | 0, 18); x01 ^= rotl(x00 + x03 | 0, 7); x02 ^= rotl(x01 + x00 | 0, 9); x03 ^= rotl(x02 + x01 | 0, 13); x00 ^= rotl(x03 + x02 | 0, 18); x06 ^= rotl(x05 + x04 | 0, 7); x07 ^= rotl(x06 + x05 | 0, 9); x04 ^= rotl(x07 + x06 | 0, 13); x05 ^= rotl(x04 + x07 | 0, 18); x11 ^= rotl(x10 + x09 | 0, 7); x08 ^= rotl(x11 + x10 | 0, 9); x09 ^= rotl(x08 + x11 | 0, 13); x10 ^= rotl(x09 + x08 | 0, 18); x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9); x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18); } out[oi++] = y00 + x00 | 0; out[oi++] = y01 + x01 | 0; out[oi++] = y02 + x02 | 0; out[oi++] = y03 + x03 | 0; out[oi++] = y04 + x04 | 0; out[oi++] = y05 + x05 | 0; out[oi++] = y06 + x06 | 0; out[oi++] = y07 + x07 | 0; out[oi++] = y08 + x08 | 0; out[oi++] = y09 + x09 | 0; out[oi++] = y10 + x10 | 0; out[oi++] = y11 + x11 | 0; out[oi++] = y12 + x12 | 0; out[oi++] = y13 + x13 | 0; out[oi++] = y14 + x14 | 0; out[oi++] = y15 + x15 | 0; } function BlockMix(input, ii, out, oi, r) { let head = oi + 0; let tail = oi + 16 * r; for (let i = 0; i < 16; i++) out[tail + i] = input[ii + (2 * r - 1) * 16 + i]; for (let i = 0; i < r; i++, head += 16, ii += 16) { XorAndSalsa(out, tail, input, ii, out, head); if (i > 0) tail += 16; XorAndSalsa(out, head, input, ii += 16, out, tail); } } function scryptInit(password, salt, _opts) { const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = checkOpts({ dkLen: 32, asyncTick: 10, maxmem: 1024 ** 3 + 1024 }, _opts); anumber(N, "N"); anumber(r, "r"); anumber(p, "p"); anumber(dkLen, "dkLen"); anumber(asyncTick, "asyncTick"); anumber(maxmem, "maxmem"); if (onProgress !== void 0 && typeof onProgress !== "function") throw new Error("progressCb must be a function"); const blockSize = 128 * r; const blockSize32 = blockSize / 4; const pow32 = Math.pow(2, 32); if (N <= 1 || (N & N - 1) !== 0 || N > pow32) throw new Error("\"N\" expected a power of 2, and 2^1 <= N <= 2^32"); if (p < 1 || p > (pow32 - 1) * 32 / blockSize) throw new Error("\"p\" expected integer 1..((2^32 - 1) * 32) / (128 * r)"); if (dkLen < 1 || dkLen > (pow32 - 1) * 32) throw new Error("\"dkLen\" expected integer 1..(2^32 - 1) * 32"); if (blockSize * (N + p) > maxmem) throw new Error("\"maxmem\" limit was hit, expected 128*r*(N+p) <= \"maxmem\"=" + maxmem); const B = pbkdf2(sha256, password, salt, { c: 1, dkLen: blockSize * p }); const B32 = u32(B); const V = u32(new Uint8Array(blockSize * N)); const tmp = u32(new Uint8Array(blockSize)); let blockMixCb = () => {}; if (onProgress) { const totalBlockMix = 2 * N * p; const callbackPer = Math.max(Math.floor(totalBlockMix / 1e4), 1); let blockMixCnt = 0; blockMixCb = () => { blockMixCnt++; if (onProgress && (!(blockMixCnt % callbackPer) || blockMixCnt === totalBlockMix)) onProgress(blockMixCnt / totalBlockMix); }; } return { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick }; } function scryptOutput(password, dkLen, B, V, tmp) { const res = pbkdf2(sha256, password, B, { c: 1, dkLen }); clean(B, V, tmp); return res; } /** * Scrypt KDF from RFC 7914. Async version. See {@link ScryptOpts}. * @example * await scryptAsync('password', 'salt', { N: 2**18, r: 8, p: 1, dkLen: 32 }); */ async function scryptAsync(password, salt, opts) { const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick } = scryptInit(password, salt, opts); swap32IfBE(B32); for (let pi = 0; pi < p; pi++) { const Pi = blockSize32 * pi; for (let i = 0; i < blockSize32; i++) V[i] = B32[Pi + i]; let pos = 0; await asyncLoop(N - 1, asyncTick, () => { BlockMix(V, pos, V, pos += blockSize32, r); blockMixCb(); }); BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); blockMixCb(); await asyncLoop(N, asyncTick, () => { const j = (B32[Pi + blockSize32 - 16] & N - 1) >>> 0; for (let k = 0; k < blockSize32; k++) tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k]; BlockMix(tmp, 0, B32, Pi, r); blockMixCb(); }); } swap32IfBE(B32); return scryptOutput(password, dkLen, B, V, tmp); } const hexadecimal = "0123456789abcdef"; const hex = { encode: (data) => { if (typeof data === "string") data = new TextEncoder().encode(data); if (data.byteLength === 0) return ""; const buffer = new Uint8Array(data); let result = ""; for (const byte of buffer) result += byte.toString(16).padStart(2, "0"); return result; }, decode: (data) => { if (!data) return ""; if (typeof data === "string") { if (data.length % 2 !== 0) throw new Error("Invalid hexadecimal string"); if (!(/* @__PURE__ */ new RegExp(`^[${hexadecimal}]+$`)).test(data)) throw new Error("Invalid hexadecimal string"); const result = new Uint8Array(data.length / 2); for (let i = 0; i < data.length; i += 2) result[i / 2] = parseInt(data.slice(i, i + 2), 16); return new TextDecoder().decode(result); } return new TextDecoder().decode(data); } }; var BetterAuthError = class extends Error { constructor(message, cause) { super(message); this.name = "BetterAuthError"; this.message = message; this.cause = cause; this.stack = ""; } }; function expandAlphabet(alphabet) { switch (alphabet) { case "a-z": return "abcdefghijklmnopqrstuvwxyz"; case "A-Z": return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; case "0-9": return "0123456789"; case "-_": return "-_"; default: throw new Error(`Unsupported alphabet: ${alphabet}`); } } function createRandomStringGenerator(...baseAlphabets) { const baseCharSet = baseAlphabets.map(expandAlphabet).join(""); if (baseCharSet.length === 0) throw new Error("No valid characters provided for random string generation."); const baseCharSetLength = baseCharSet.length; return (length, ...alphabets) => { if (length <= 0) throw new Error("Length must be a positive integer."); let charSet = baseCharSet; let charSetLength = baseCharSetLength; if (alphabets.length > 0) { charSet = alphabets.map(expandAlphabet).join(""); charSetLength = charSet.length; } const maxValid = Math.floor(256 / charSetLength) * charSetLength; const buf = new Uint8Array(length * 2); const bufLength = buf.length; let result = ""; let bufIndex = bufLength; let rand; while (result.length < length) { if (bufIndex >= bufLength) { crypto.getRandomValues(buf); bufIndex = 0; } rand = buf[bufIndex++]; if (rand < maxValid) result += charSet[rand % charSetLength]; } return result; }; } createRandomStringGenerator("a-z", "0-9", "A-Z", "-_"); function constantTimeEqual(a, b) { const aBuffer = new Uint8Array(a); const bBuffer = new Uint8Array(b); let c = aBuffer.length ^ bBuffer.length; const length = Math.max(aBuffer.length, bBuffer.length); for (let i = 0; i < length; i++) c |= (i < aBuffer.length ? aBuffer[i] : 0) ^ (i < bBuffer.length ? bBuffer[i] : 0); return c === 0; } const config = { N: 16384, r: 16, p: 1, dkLen: 64 }; async function generateKey(password, salt) { return await scryptAsync(password.normalize("NFKC"), salt, { N: config.N, p: config.p, r: config.r, dkLen: config.dkLen, maxmem: 128 * config.N * config.r * 2 }); } const hashPassword = async (password) => { const salt = hex.encode(crypto.getRandomValues(new Uint8Array(16))); const key = await generateKey(password, salt); return `${salt}:${hex.encode(key)}`; }; const verifyPassword = async ({ hash, password }) => { const [salt, key] = hash.split(":"); if (!salt || !key) throw new BetterAuthError("Invalid password hash"); return constantTimeEqual(await generateKey(password, salt), hexToBytes(key)); }; //#endregion //#region ../blade-auth/dist/triggers/account.js const primeId$1 = async ({ query, cookies }) => { if (Object.keys(query.with || {}).length > 0) return query; query.with = {}; const accountId = cookies.account || (await getSessionCookie(cookies)).accountId; query.with.id = accountId; return query; }; var account_default = (authConfig) => { return triggers({ get: primeId$1, add: async ({ query, parentTrigger, setCookie }) => { if (!query.with) throw new Error("A `with` instruction must be given."); if (Array.isArray(query.with)) throw new MultipleWithInstructionsError(); const id = getRecordIdentifier("acc"); Object.assign(query.with, { id, password: await hashPassword(query.with.password), emailVerified: false, emailVerificationToken: generateUniqueId(), emailVerificationSentAt: /* @__PURE__ */ new Date() }); if (!parentTrigger) setCookie("account", id); return query; }, set: async (options) => { const { query } = options; const { get } = options.client; await primeId$1(options); if (!query.with) throw new Error("A `with` instruction must be given."); if (!query.to) throw new Error("A `to` instruction must be given."); if (Array.isArray(query.with)) throw new MultipleWithInstructionsError(); avoidEmptyFields(query.to, ["password"]); const operation = query.to.emailVerificationSentAt ? "EMAIL_VERIFICATION_RESEND" : query.to.emailVerified === true ? "EMAIL_VERIFICATION_DONE" : query.to.password === null ? "PASSWORD_RESET" : query.to.password && query.with?.emailVerificationToken ? "PASSWORD_RESET_DONE" : query.to.password ? "PASSWORD_CHANGE" : null; options.context.set("operation", operation); if (operation === "EMAIL_VERIFICATION_RESEND" || operation === "EMAIL_VERIFICATION_DONE" || operation === "PASSWORD_RESET" || operation === "PASSWORD_RESET_DONE") { if (!(operation === "EMAIL_VERIFICATION_RESEND" || operation === "PASSWORD_RESET")) { const { emailVerificationToken } = query.with || {}; if (!emailVerificationToken) throw new EmptyFieldsError({ message: "For this action, the field `with.emailVerificationToken` must be provided.", fields: ["emailVerificationToken"] }); const account = await get.account.with({ emailVerificationToken }); if (!account) throw new InvalidFieldsError({ fields: ["id", "currentPassword"] }); if (!query.to.ronin) query.to.ronin = {}; query.to.ronin.updatedBy = account.id; } } if (operation) console.debug(`Performing \`${operation}\` on selector \`${JSON.stringify(query.with)}\``); const currentPassword = query.to.currentPassword; delete query.to.currentPassword; if (currentPassword && !query.to.password) throw new EmptyFieldsError({ fields: ["password"] }); if (query.to.emailVerificationToken) throw new InvalidFieldsError({ fields: ["emailVerificationToken"] }); if (operation === "EMAIL_VERIFICATION_DONE") { const account = await get.account.with.id(query.with.id); if (!account) throw new RecordNotFoundError({ message: "Account subject to verification does not exist." }); if (account.emailVerificationToken !== query.with?.emailVerificationToken) throw new InvalidFieldsError({ message: "Invalid verification token provided.", fields: ["emailVerificationToken"] }); if (account.emailVerified) throw new SetNotAllowedError({ message: "This account already has a verified email address." }); } if (operation === "EMAIL_VERIFICATION_RESEND" || operation === "PASSWORD_RESET") { const account = await get.account.with(query.with); if (!account) throw new RecordNotFoundError({ fields: ["email"] }); if (operation === "EMAIL_VERIFICATION_RESEND" && account.emailVerified) throw new SetNotAllowedError({ message: "This account already has a verified email address." }); const newDate = query.to.emailVerificationSentAt ? new Date(query.to.emailVerificationSentAt) : /* @__PURE__ */ new Date(); const previousDate = new Date(account.emailVerificationSentAt); if (newDate < previousDate) throw new InvalidFieldsError({ message: "The date provided for `emailVerificationSentAt` must not be in the past.", fields: ["emailVerificationSentAt"] }); if (newDate.getTime() < previousDate.getTime() + EMAIL_VERIFICATION_COOLDOWN) throw new TooManyRequestsError({ fields: ["emailVerificationSentAt"] }); query.to = { ...query.to, emailVerificationToken: generateUniqueId(), emailVerificationSentAt: newDate }; } if (operation === "PASSWORD_CHANGE" || operation === "PASSWORD_RESET_DONE") { avoidEmptyFields(query.to); if (operation === "PASSWORD_CHANGE") { const match = await get.account.with(query.with); if (!(query.with?.emailVerificationToken || currentPassword)) throw new EmptyFieldsError({ fields: ["currentPassword"] }); if (!(match && (query.with?.emailVerificationToken || await verifyPassword({ hash: match.password || "", password: currentPassword })))) throw new InvalidFieldsError({ fields: ["id", "currentPassword"] }); } query.to.password = await hashPassword(query.to.password); } else delete query.to.password; return query; }, remove: async (options) => { const { query, cookies, setCookie } = options; await primeId$1(options); if (!options.cookies.session) query.with.emailVerified = false; if (cookies.account) setCookie("account", null); return query; }, followingAdd: async (options) => { const { records } = options; for (const account of records) await authConfig?.sendEmail?.({ account, type: "ACCOUNT_CREATION", token: account.emailVerificationToken, options }); }, followingSet: async (options) => { const { query, context, records } = options; const operation = context.get("operation"); if (!(operation === "PASSWORD_RESET" || operation === "EMAIL_VERIFICATION_RESEND")) return; const { emailVerificationToken } = query.to; for (const account of records) await authConfig?.sendEmail?.({ account, type: operation === "EMAIL_VERIFICATION_RESEND" ? "ACCOUNT_CREATION" : operation, token: emailVerificationToken, options }); } }); }; //#endregion //#region ../blade-auth/dist/triggers/session.js const primeId = async ({ query, cookies, multipleRecords }) => { if (query.with) return query; query.with = {}; const { sessionId, accountId } = await getSessionCookie(cookies); if (multipleRecords) query.with.account = accountId; else query.with.id = sessionId; return query; }; var session_default = (_authConfig) => { return triggers({ get: primeId, add: async ({ query, navigator, client, parentTrigger, context, cookies, setCookie }) => { const { userAgent } = navigator; const { get } = client; if (!query.with) throw new Error("A `with` instruction must be given."); if (Array.isArray(query.with)) throw new MultipleWithInstructionsError(); query.with = { ...query.with, id: getRecordIdentifier("ses"), browser: userAgent?.browser || null, browserVersion: userAgent?.browserVersion || null, os: userAgent?.os || null, osVersion: userAgent?.osVersion || null, deviceType: userAgent?.deviceType || null }; if (!parentTrigger) { const providedAccount = query.with.account; const account = await get.account.with("password" in providedAccount ? { email: providedAccount.email } : { emailVerificationToken: providedAccount.emailVerificationToken }); const error = new InvalidFieldsError({ fields: Object.keys(providedAccount).map((field) => `account.${field}`) }); if (!account) throw error; if ("password" in providedAccount && !await verifyPassword({ hash: account.password, password: providedAccount.password })) throw error; if ("emailVerificationToken" in providedAccount) if (account.emailVerificationToken === providedAccount.emailVerificationToken) { context.set("accountToVerify", account.id); context.set("emailVerificationToken", providedAccount.emailVerificationToken); } else throw error; query.with.account = account.id; } setCookie("session", await signJWT({ iss: "ronin", sub: query.with.id, aud: query.with.account, iat: Math.floor(Date.now() / 1e3) }, AUTH_SECRET)); if (cookies.account) setCookie("account", null); return query; }, afterAdd: ({ query, client, context }) => { if (!query.with) throw new Error("A `with` instruction must be given."); if (Array.isArray(query.with)) throw new MultipleWithInstructionsError(); const { set } = client; const accountToVerify = context.get("accountToVerify"); if (accountToVerify) { const emailVerificationToken = context.get("emailVerificationToken"); return () => [set.account({ with: { id: accountToVerify, emailVerified: false, emailVerificationToken }, to: { emailVerified: true } })]; } return []; }, remove: async (options) => { const { query, setCookie } = options; await primeId(options); setCookie("session", null); return query; } }); }; //#endregion export { account_default as getAccountTriggers, session_default as getSessionTriggers };