blade
Version:
React at the edge.
1,062 lines (1,058 loc) • 35.6 kB
JavaScript
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 };