@stacksjs/security 0.70.163 → 0.70.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,7 +48,7 @@ For help, discussion about best practices, or any other conversation that would
48
48
 
49
49
  For casual chit-chat with others using this package:
50
50
 
51
- [Join the Stacks Discord Server](https://discord.gg/stacksjs)
51
+ [Join the Stacks Discord Server](https://stacksjs.com/discord)
52
52
 
53
53
  ## 🙏🏼 Credits
54
54
 
package/dist/hash.d.ts CHANGED
@@ -43,27 +43,6 @@ export declare function argon2Encode(password: string, options?: {
43
43
  memory?: number
44
44
  time?: number
45
45
  }): Promise<string>;
46
- /**
47
- * Verify a password against an Argon2 hash.
48
- *
49
- * Refuses to verify hashes that aren't actually Argon2 — `Bun.password.verify`
50
- * auto-detects the algorithm from the hash prefix, which means a stray
51
- * call `argon2Verify(password, bcryptHash)` would return `true` if the
52
- * password matched. The algorithm guard restores the function-name
53
- * contract (stacksjs/stacks#1861 H-8).
54
- *
55
- * @deprecated Use check() instead which auto-detects the algorithm
56
- */
57
- export declare function argon2Verify(password: string, hash: string): Promise<boolean>;
58
- /**
59
- * Verify a password against a bcrypt hash.
60
- *
61
- * Refuses to verify hashes that aren't actually bcrypt — see the note
62
- * on {@link argon2Verify} (stacksjs/stacks#1861 H-8).
63
- *
64
- * @deprecated Use check() instead which auto-detects the algorithm
65
- */
66
- export declare function bcryptVerify(password: string, hash: string): Promise<boolean>;
67
46
  /**
68
47
  * Verify a password against a base64 encoded string
69
48
  * Note: base64 is NOT a secure password hash - only for legacy support
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var f=import.meta.require;import{Buffer as M}from"buffer";import{config as H}from"@stacksjs/config";import{decrypt as B,encrypt as JJ}from"ts-security-crypto";function W(J){return J.startsWith("base64:")?J.slice(7):J}var C=1,_=600000,S=32,O=16,R=12,v=64,z=new Map;function b(J,U,Q){return`${Q}:${M.from(U).toString("base64")}:${J}`}async function E(J,U,Q){let Z=b(J,U,Q),$=z.get(Z);if($)return $.hits++,z.delete(Z),z.set(Z,$),$.key;let G=await crypto.subtle.importKey("raw",new TextEncoder().encode(J),{name:"PBKDF2"},!1,["deriveKey"]),X=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(U),iterations:Q,hash:"SHA-256"},G,{name:"AES-GCM",length:S*8},!1,["encrypt","decrypt"]);if(z.set(Z,{key:X,hits:1}),z.size>v){let F=z.keys().next().value;if(F!==void 0)z.delete(F)}return X}async function y(J,U){let Q=crypto.getRandomValues(new Uint8Array(O)),Z=crypto.getRandomValues(new Uint8Array(R)),$=await E(U,Q,_),G=await crypto.subtle.encrypt({name:"AES-GCM",iv:Z},$,new TextEncoder().encode(J)),X=new Uint8Array(G),F=new Uint8Array(1+O+R+X.byteLength);return F[0]=C,F.set(Q,1),F.set(Z,1+O),F.set(X,1+O+R),M.from(F).toString("base64")}async function m(J,U){let Q=M.from(J,"base64");if(Q.length<1+O+R+16)return null;if(Q[0]!==C)return null;let Z=new Uint8Array(Q.subarray(1,1+O)),$=new Uint8Array(Q.subarray(1+O,1+O+R)),G=new Uint8Array(Q.subarray(1+O+R)),X=await E(U,Z,_);try{let F=await crypto.subtle.decrypt({name:"AES-GCM",iv:$},X,G);return new TextDecoder().decode(F)}catch{return null}}async function QJ(J,U){if(!J&&J!=="")throw Error("encrypt() requires a string message");let Q=U||H.app.key;if(!Q)throw Error("APP_KEY is not defined");return await y(J,W(Q))}async function UJ(J,U){if(!J)throw Error("decrypt() requires a non-empty encrypted string");let Q=U||H.app.key;if(!Q)throw Error("APP_KEY is not defined");let Z=W(Q),$=await m(J,Z);if($!==null)return $;try{return await B(J,Z)}catch(G){if(Z!==Q)try{return await B(J,Q)}catch{}throw G}}function ZJ(){z.clear()}import{timingSafeEqual as K}from"crypto";import{base64Decode as u,base64Encode as FJ,hashPassword as T,md5 as l,verifyPassword as L}from"ts-security-crypto";var D=null;function q(){if(D)return D;try{let{hashing:J}=f("@stacksjs/config");return D=J||{},D}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),D={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},D}}function V(J){if(!J||typeof J!=="string")return"unknown";if(J.startsWith("$2a$")||J.startsWith("$2b$")||J.startsWith("$2y$"))return"bcrypt";if(J.startsWith("$argon2id$"))return"argon2id";if(J.startsWith("$argon2i$"))return"argon2i";if(J.startsWith("$argon2d$"))return"argon2d";return"unknown"}function g(J){let U=V(J),Q={};if(U==="bcrypt"){let Z=J.match(/^\$2[aby]\$(\d{2})\$/);if(Z&&Z[1]!==void 0)Q.rounds=Number.parseInt(Z[1],10)}else if(U==="argon2id"||U==="argon2i"||U==="argon2d"){let Z=J.match(/v=(\d+)/),$=J.match(/m=(\d+)/),G=J.match(/t=(\d+)/),X=J.match(/p=(\d+)/);if(Z&&Z[1]!==void 0)Q.version=Number.parseInt(Z[1],10);if($&&$[1]!==void 0)Q.memory=Number.parseInt($[1],10);if(G&&G[1]!==void 0)Q.rounds=Number.parseInt(G[1],10);if(X&&X[1]!==void 0)Q.parallelism=Number.parseInt(X[1],10)}return{algorithm:U,options:Q}}function NJ(J,U){let Q=g(J),Z=q(),$=U?.algorithm||Z.driver||"bcrypt",G=(j)=>{if(j==="argon2")return"argon2id";return j},X=G(Q.algorithm),F=G($);if(X!==F)return!0;if(X==="bcrypt"){let j=U?.rounds||Z.bcrypt?.rounds||12;if(Q.options.rounds!==j)return!0}if(X.startsWith("argon2")){let j=U?.memory||Z.argon2?.memory||65536,x=U?.time||Z.argon2?.time||2;if(Q.options.memory!==j||Q.options.rounds!==x)return!0}return!1}async function OJ(J,U){let Q=q(),Z=U?.algorithm||Q.driver||"bcrypt";if(Z==="bcrypt")return await c(J,U?.rounds);if(Z==="argon2"||Z==="argon2id"||Z==="argon2i"||Z==="argon2d")return await p(J,{type:Z==="argon2"?"argon2id":Z,memory:U?.memory,time:U?.time});throw Error(`Unsupported hashing algorithm: ${Z}`)}async function jJ(J,U){if(!J||!U)return!1;if(V(U)==="unknown")try{return await L(J,U)}catch{return!1}return await L(J,U)}async function c(J,U){let Q=q(),Z=U||Q.bcrypt?.rounds||12;return await T(J,{algorithm:"bcrypt",cost:Z})}async function p(J,U){let Q=q(),Z=U?.type||"argon2id",$=U?.memory||Q.argon2?.memory||65536,G=U?.time||Q.argon2?.time||2;return await T(J,{algorithm:Z,memoryCost:$,timeCost:G})}async function zJ(J,U){let Q=V(U);if(Q!=="argon2"&&Q!=="argon2i"&&Q!=="argon2id"&&Q!=="argon2d")return!1;return await L(J,U)}async function RJ(J,U){if(V(U)!=="bcrypt")return!1;return await L(J,U)}function DJ(J,U){let Q=u(U),Z=Buffer.from(Q),$=Buffer.from(J);if(Z.length!==$.length)return!1;return K(Z,$)}function LJ(J){return l(J)}function qJ(J,U){let Q=Buffer.from(J),Z=Buffer.from(U);if(Q.length!==Z.length)return K(Q,Q),!1;return K(Q,Z)}import{generateKey as d}from"ts-security-crypto";function MJ(){return d(32)}import{createHmac as n,timingSafeEqual as I}from"crypto";class N extends Error{reason;constructor(J,U="mismatch"){super(J);this.name="InvalidWebhookSignature",this.reason=U}}function P(J){if(!/^[0-9a-f]*$/i.test(J)||J.length%2!==0)throw new N("Signature is not valid hex","malformed");return Buffer.from(J,"hex")}function w(J,U){if(J.length!==U.length)return I(J,J),!1;return I(J,U)}function Y(J,U,Q){return n(Q,J).update(U).digest()}function h(J){let U=J.split(","),Q=Number.NaN,Z=[];for(let $ of U){let G=$.indexOf("=");if(G===-1)continue;let X=$.slice(0,G).trim(),F=$.slice(G+1).trim();if(X==="t")Q=Number.parseInt(F,10);else if(X==="v1")Z.push(F)}if(!Number.isFinite(Q)||Z.length===0)throw new N("Stripe-Signature header is malformed","malformed");return{timestamp:Q,signatures:Z}}function t(J,U,Q,Z={}){if(!J)throw new N("Stripe webhook secret is empty","missing");if(!U)throw new N("Stripe-Signature header is missing","missing");let{timestamp:$,signatures:G}=h(U),X=Z.toleranceSeconds??300;if(Math.floor((Z.now??Date.now())/1000)-$>X)throw new N("Stripe webhook timestamp is outside tolerance window","expired");let j=typeof Q==="string"?Q:Q.toString("utf8"),x=Y(J,`${$}.${j}`,"sha256");for(let k of G){let A;try{A=P(k)}catch{continue}if(w(x,A))return!0}throw new N("Stripe webhook signature did not match","mismatch")}function o(J,U,Q){if(!J)throw new N("GitHub webhook secret is empty","missing");if(!U)throw new N("X-Hub-Signature-256 header is missing","missing");let Z="sha256=";if(!U.startsWith(Z))throw new N("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let $=P(U.slice(Z.length)),G=Y(J,Q,"sha256");if(!w(G,$))throw new N("GitHub webhook signature did not match","mismatch");return!0}function i(J,U,Q,Z="sha256"){if(!J)throw new N("HMAC secret is empty","missing");if(!Q)throw new N("Signature header is missing","missing");let $=Q.indexOf("="),G=$!==-1&&/^[a-z0-9]+$/i.test(Q.slice(0,$))?Q.slice($+1):Q,X=P(G),F=Y(J,U,Z);if(!w(F,X))throw new N("HMAC signature did not match","mismatch");return!0}function wJ(J,U,Q,Z,$={}){switch(J){case"stripe":return t(U,Q,Z,$);case"github":return o(U,Q,Z);case"generic":return i(U,Z,Q,$.algorithm??"sha256");default:throw new N(`Unknown webhook provider: ${String(J)}`,"malformed")}}export{wJ as verifyWebhook,t as verifyStripe,i as verifyHmac,jJ as verifyHash,o as verifyGithub,qJ as timingSafeEqualString,NJ as needsRehash,LJ as md5Encode,OJ as makeHash,OJ as make,JJ as legacyEncrypt,g as info,NJ as hashNeedsRehash,OJ as hashMake,g as hashInfo,V as hashDetectAlgorithm,jJ as hashCheck,MJ as generateAppKey,QJ as encrypt,V as detectAlgorithm,UJ as decrypt,jJ as check,RJ as bcryptVerify,c as bcryptEncode,DJ as base64Verify,FJ as base64Encode,zJ as argon2Verify,p as argon2Encode,ZJ as _resetCryptCacheForTests,N as InvalidWebhookSignature};
2
+ var f=import.meta.require;import{Buffer as V}from"buffer";import{config as A}from"@stacksjs/config";import{decrypt as Y,encrypt as JJ}from"ts-security-crypto";function B(J){return J.startsWith("base64:")?J.slice(7):J}var H=1,W=600000,S=32,O=16,R=12,v=64,z=new Map;function b(J,U,Q){return`${Q}:${V.from(U).toString("base64")}:${J}`}async function C(J,U,Q){let Z=b(J,U,Q),$=z.get(Z);if($)return $.hits++,z.delete(Z),z.set(Z,$),$.key;let G=await crypto.subtle.importKey("raw",new TextEncoder().encode(J),{name:"PBKDF2"},!1,["deriveKey"]),X=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(U),iterations:Q,hash:"SHA-256"},G,{name:"AES-GCM",length:S*8},!1,["encrypt","decrypt"]);if(z.set(Z,{key:X,hits:1}),z.size>v){let F=z.keys().next().value;if(F!==void 0)z.delete(F)}return X}async function y(J,U){let Q=crypto.getRandomValues(new Uint8Array(O)),Z=crypto.getRandomValues(new Uint8Array(R)),$=await C(U,Q,W),G=await crypto.subtle.encrypt({name:"AES-GCM",iv:Z},$,new TextEncoder().encode(J)),X=new Uint8Array(G),F=new Uint8Array(1+O+R+X.byteLength);return F[0]=H,F.set(Q,1),F.set(Z,1+O),F.set(X,1+O+R),V.from(F).toString("base64")}async function m(J,U){let Q=V.from(J,"base64");if(Q.length<1+O+R+16)return null;if(Q[0]!==H)return null;let Z=new Uint8Array(Q.subarray(1,1+O)),$=new Uint8Array(Q.subarray(1+O,1+O+R)),G=new Uint8Array(Q.subarray(1+O+R)),X=await C(U,Z,W);try{let F=await crypto.subtle.decrypt({name:"AES-GCM",iv:$},X,G);return new TextDecoder().decode(F)}catch{return null}}async function QJ(J,U){if(!J&&J!=="")throw Error("encrypt() requires a string message");let Q=U||A.app.key;if(!Q)throw Error("APP_KEY is not defined");return await y(J,B(Q))}async function UJ(J,U){if(!J)throw Error("decrypt() requires a non-empty encrypted string");let Q=U||A.app.key;if(!Q)throw Error("APP_KEY is not defined");let Z=B(Q),$=await m(J,Z);if($!==null)return $;try{return await Y(J,Z)}catch(G){if(Z!==Q)try{return await Y(J,Q)}catch{}throw G}}function ZJ(){z.clear()}import{timingSafeEqual as w}from"crypto";import{base64Decode as l,base64Encode as FJ,hashPassword as E,md5 as u,verifyPassword as _}from"ts-security-crypto";var D=null;function L(){if(D)return D;try{let{hashing:J}=f("@stacksjs/config");return D=J||{},D}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),D={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},D}}function T(J){if(!J||typeof J!=="string")return"unknown";if(J.startsWith("$2a$")||J.startsWith("$2b$")||J.startsWith("$2y$"))return"bcrypt";if(J.startsWith("$argon2id$"))return"argon2id";if(J.startsWith("$argon2i$"))return"argon2i";if(J.startsWith("$argon2d$"))return"argon2d";return"unknown"}function g(J){let U=T(J),Q={};if(U==="bcrypt"){let Z=J.match(/^\$2[aby]\$(\d{2})\$/);if(Z&&Z[1]!==void 0)Q.rounds=Number.parseInt(Z[1],10)}else if(U==="argon2id"||U==="argon2i"||U==="argon2d"){let Z=J.match(/v=(\d+)/),$=J.match(/m=(\d+)/),G=J.match(/t=(\d+)/),X=J.match(/p=(\d+)/);if(Z&&Z[1]!==void 0)Q.version=Number.parseInt(Z[1],10);if($&&$[1]!==void 0)Q.memory=Number.parseInt($[1],10);if(G&&G[1]!==void 0)Q.rounds=Number.parseInt(G[1],10);if(X&&X[1]!==void 0)Q.parallelism=Number.parseInt(X[1],10)}return{algorithm:U,options:Q}}function NJ(J,U){let Q=g(J),Z=L(),$=U?.algorithm||Z.driver||"bcrypt",G=(j)=>{if(j==="argon2")return"argon2id";return j},X=G(Q.algorithm),F=G($);if(X!==F)return!0;if(X==="bcrypt"){let j=U?.rounds||Z.bcrypt?.rounds||12;if(Q.options.rounds!==j)return!0}if(X.startsWith("argon2")){let j=U?.memory||Z.argon2?.memory||65536,q=U?.time||Z.argon2?.time||2;if(Q.options.memory!==j||Q.options.rounds!==q)return!0}return!1}async function OJ(J,U){let Q=L(),Z=U?.algorithm||Q.driver||"bcrypt";if(Z==="bcrypt")return await c(J,U?.rounds);if(Z==="argon2"||Z==="argon2id"||Z==="argon2i"||Z==="argon2d")return await p(J,{type:Z==="argon2"?"argon2id":Z,memory:U?.memory,time:U?.time});throw Error(`Unsupported hashing algorithm: ${Z}`)}async function jJ(J,U){if(!J||!U)return!1;if(T(U)==="unknown")try{return await _(J,U)}catch{return!1}return await _(J,U)}async function c(J,U){let Q=L(),Z=U||Q.bcrypt?.rounds||12;return await E(J,{algorithm:"bcrypt",cost:Z})}async function p(J,U){let Q=L(),Z=U?.type||"argon2id",$=U?.memory||Q.argon2?.memory||65536,G=U?.time||Q.argon2?.time||2;return await E(J,{algorithm:Z,memoryCost:$,timeCost:G})}function zJ(J,U){let Q=l(U),Z=Buffer.from(Q),$=Buffer.from(J);if(Z.length!==$.length)return!1;return w(Z,$)}function RJ(J){return u(J)}function DJ(J,U){let Q=Buffer.from(J),Z=Buffer.from(U);if(Q.length!==Z.length)return w(Q,Q),!1;return w(Q,Z)}import{generateKey as d}from"ts-security-crypto";function VJ(){return d(32)}import{createHmac as n,timingSafeEqual as I}from"crypto";class N extends Error{reason;constructor(J,U="mismatch"){super(J);this.name="InvalidWebhookSignature",this.reason=U}}function x(J){if(!/^[0-9a-f]*$/i.test(J)||J.length%2!==0)throw new N("Signature is not valid hex","malformed");return Buffer.from(J,"hex")}function M(J,U){if(J.length!==U.length)return I(J,J),!1;return I(J,U)}function P(J,U,Q){return n(Q,J).update(U).digest()}function h(J){let U=J.split(","),Q=Number.NaN,Z=[];for(let $ of U){let G=$.indexOf("=");if(G===-1)continue;let X=$.slice(0,G).trim(),F=$.slice(G+1).trim();if(X==="t")Q=Number.parseInt(F,10);else if(X==="v1")Z.push(F)}if(!Number.isFinite(Q)||Z.length===0)throw new N("Stripe-Signature header is malformed","malformed");return{timestamp:Q,signatures:Z}}function t(J,U,Q,Z={}){if(!J)throw new N("Stripe webhook secret is empty","missing");if(!U)throw new N("Stripe-Signature header is missing","missing");let{timestamp:$,signatures:G}=h(U),X=Z.toleranceSeconds??300;if(Math.floor((Z.now??Date.now())/1000)-$>X)throw new N("Stripe webhook timestamp is outside tolerance window","expired");let j=typeof Q==="string"?Q:Q.toString("utf8"),q=P(J,`${$}.${j}`,"sha256");for(let k of G){let K;try{K=x(k)}catch{continue}if(M(q,K))return!0}throw new N("Stripe webhook signature did not match","mismatch")}function o(J,U,Q){if(!J)throw new N("GitHub webhook secret is empty","missing");if(!U)throw new N("X-Hub-Signature-256 header is missing","missing");let Z="sha256=";if(!U.startsWith(Z))throw new N("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let $=x(U.slice(Z.length)),G=P(J,Q,"sha256");if(!M(G,$))throw new N("GitHub webhook signature did not match","mismatch");return!0}function r(J,U,Q,Z="sha256"){if(!J)throw new N("HMAC secret is empty","missing");if(!Q)throw new N("Signature header is missing","missing");let $=Q.indexOf("="),G=$!==-1&&/^[a-z0-9]+$/i.test(Q.slice(0,$))?Q.slice($+1):Q,X=x(G),F=P(J,U,Z);if(!M(F,X))throw new N("HMAC signature did not match","mismatch");return!0}function MJ(J,U,Q,Z,$={}){switch(J){case"stripe":return t(U,Q,Z,$);case"github":return o(U,Q,Z);case"generic":return r(U,Z,Q,$.algorithm??"sha256");default:throw new N(`Unknown webhook provider: ${String(J)}`,"malformed")}}export{MJ as verifyWebhook,t as verifyStripe,r as verifyHmac,jJ as verifyHash,o as verifyGithub,DJ as timingSafeEqualString,NJ as needsRehash,RJ as md5Encode,OJ as makeHash,OJ as make,JJ as legacyEncrypt,g as info,NJ as hashNeedsRehash,OJ as hashMake,g as hashInfo,T as hashDetectAlgorithm,jJ as hashCheck,VJ as generateAppKey,QJ as encrypt,T as detectAlgorithm,UJ as decrypt,jJ as check,c as bcryptEncode,zJ as base64Verify,FJ as base64Encode,p as argon2Encode,ZJ as _resetCryptCacheForTests,N as InvalidWebhookSignature};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/security",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.163",
5
+ "version": "0.70.164",
6
6
  "description": "The Stacks framework security.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -55,10 +55,10 @@
55
55
  "ts-security-crypto": "0.0.2"
56
56
  },
57
57
  "devDependencies": {
58
- "@stacksjs/config": "0.70.163",
58
+ "@stacksjs/config": "0.70.164",
59
59
  "better-dx": "^0.2.17",
60
- "@stacksjs/env": "0.70.163",
61
- "@stacksjs/types": "0.70.163",
62
- "@stacksjs/validation": "0.70.163"
60
+ "@stacksjs/env": "0.70.164",
61
+ "@stacksjs/types": "0.70.164",
62
+ "@stacksjs/validation": "0.70.164"
63
63
  }
64
64
  }