@stacksjs/security 0.75.16 → 0.75.18

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
@@ -19,9 +19,10 @@ bun install -d @stacksjs/security
19
19
  Now, you can easily access it in your project:
20
20
 
21
21
  ```js
22
- import { base64Encode, base64Verify, bcryptEncode, bcryptVerify, decrypt, encrypt, makeHash, md5Encode, verifyHash } from '@stacksjs/security'
22
+ import { check, decrypt, encrypt, make } from '@stacksjs/security'
23
23
 
24
- // and more...
24
+ const hash = await make('correct horse battery staple')
25
+ const valid = await check('correct horse battery staple', hash)
25
26
  ```
26
27
 
27
28
  To view the full documentation, please visit [<https://stacksjs.com/securit>y](https://stacksjs.com/security).
package/dist/hash.d.ts CHANGED
@@ -43,15 +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 a base64 encoded string
48
- * Note: base64 is NOT a secure password hash - only for legacy support
49
- */
50
- export declare function base64Verify(password: string, hash: string): boolean;
51
- /**
52
- * Create an MD5 hash (NOT secure for passwords)
53
- */
54
- export declare function md5Encode(password: string): string;
55
46
  /**
56
47
  * Constant-time string comparison.
57
48
  *
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var T=import.meta.require;import{Buffer as d}from"buffer";import{config as S}from"@stacksjs/config";import{decrypt as E,encrypt as tr}from"ts-security-crypto";function k(r){return r.startsWith("base64:")?r.slice(7):r}var B=1,C=600000,N=32,m=16,g=12,U=64,u=new Map;function V(r,e,t){return`${t}:${d.from(e).toString("base64")}:${r}`}async function P(r,e,t){let n=V(r,e,t),i=u.get(n);if(i)return i.hits++,u.delete(n),u.set(n,i),i.key;let o=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:t,hash:"SHA-256"},o,{name:"AES-GCM",length:N*8},!1,["encrypt","decrypt"]);if(u.set(n,{key:s,hits:1}),u.size>U){let a=u.keys().next().value;if(a!==void 0)u.delete(a)}return s}async function D(r,e){let t=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(g)),i=await P(e,t,C),o=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},i,new TextEncoder().encode(r)),s=new Uint8Array(o),a=new Uint8Array(1+m+g+s.byteLength);return a[0]=B,a.set(t,1),a.set(n,1+m),a.set(s,1+m+g),d.from(a).toString("base64")}async function O(r,e){let t=d.from(r,"base64");if(t.length<1+m+g+16)return null;if(t[0]!==B)return null;let n=new Uint8Array(t.subarray(1,1+m)),i=new Uint8Array(t.subarray(1+m,1+m+g)),o=new Uint8Array(t.subarray(1+m+g)),s=await P(e,n,C);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:i},s,o);return new TextDecoder().decode(a)}catch{return null}}async function er(r,e){if(!r&&r!=="")throw Error("encrypt() requires a string message");let t=e||S.app.key;if(!t)throw Error("APP_KEY is not defined");return await D(r,k(t))}async function nr(r,e){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let t=e||S.app.key;if(!t)throw Error("APP_KEY is not defined");let n=k(t),i=await O(r,n);if(i!==null)return i;try{return await E(r,n)}catch(o){if(n!==t)try{return await E(r,t)}catch{}throw o}}function ir(){u.clear()}import{timingSafeEqual as l}from"crypto";import{base64Decode as q,base64Encode as cr,hashPassword as v,md5 as R,verifyPassword as K}from"ts-security-crypto";var p=null;function h(){if(p)return p;try{let{hashing:r}=T("@stacksjs/config");return p=r||{},p}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),p={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},p}}function M(r){if(!r||typeof r!=="string")return"unknown";if(r.startsWith("$2a$")||r.startsWith("$2b$")||r.startsWith("$2y$"))return"bcrypt";if(r.startsWith("$argon2id$"))return"argon2id";if(r.startsWith("$argon2i$"))return"argon2i";if(r.startsWith("$argon2d$"))return"argon2d";return"unknown"}function Y(r){let e=M(r),t={};if(e==="bcrypt"){let n=r.match(/^\$2[aby]\$(\d{2})\$/);if(n&&n[1]!==void 0)t.rounds=Number.parseInt(n[1],10)}else if(e==="argon2id"||e==="argon2i"||e==="argon2d"){let n=r.match(/v=(\d+)/),i=r.match(/m=(\d+)/),o=r.match(/t=(\d+)/),s=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)t.version=Number.parseInt(n[1],10);if(i&&i[1]!==void 0)t.memory=Number.parseInt(i[1],10);if(o&&o[1]!==void 0)t.rounds=Number.parseInt(o[1],10);if(s&&s[1]!==void 0)t.parallelism=Number.parseInt(s[1],10)}return{algorithm:e,options:t}}function mr(r,e){let t=Y(r),n=h(),i=e?.algorithm||n.driver||"bcrypt",o=(f)=>{if(f==="argon2")return"argon2id";return f},s=o(t.algorithm),a=o(i);if(s!==a)return!0;if(s==="bcrypt"){let f=e?.rounds||n.bcrypt?.rounds||12;if(t.options.rounds!==f)return!0}if(s.startsWith("argon2")){let f=e?.memory||n.argon2?.memory||65536,y=e?.time||n.argon2?.time||2;if(t.options.memory!==f||t.options.rounds!==y)return!0}return!1}async function fr(r,e){let t=h(),n=e?.algorithm||t.driver||"bcrypt";if(n==="bcrypt")return await F(r,e?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await G(r,{type:n==="argon2"?"argon2id":n,memory:e?.memory,time:e?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function ur(r,e){if(!r||!e)return!1;if(M(e)==="unknown")try{return await K(r,e)}catch{return!1}return await K(r,e)}async function F(r,e){let t=h(),n=e||t.bcrypt?.rounds||12;return await v(r,{algorithm:"bcrypt",cost:n})}async function G(r,e){let t=h(),n=e?.type||"argon2id",i=e?.memory||t.argon2?.memory||65536,o=e?.time||t.argon2?.time||2;return await v(r,{algorithm:n,memoryCost:i,timeCost:o})}function gr(r,e){let t=q(e),n=Buffer.from(t),i=Buffer.from(r);if(n.length!==i.length)return!1;return l(n,i)}function pr(r){return R(r)}function hr(r,e){let t=Buffer.from(r),n=Buffer.from(e);if(t.length!==n.length)return l(t,t),!1;return l(t,n)}import{generateKey as z}from"ts-security-crypto";function lr(){return z(32)}import{createHmac as I,timingSafeEqual as H}from"crypto";class c extends Error{reason;constructor(r,e="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=e}}function w(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new c("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function b(r,e){if(r.length!==e.length)return H(r,r),!1;return H(r,e)}function x(r,e,t){return I(t,r).update(e).digest()}function W(r){let e=r.split(","),t=Number.NaN,n=[];for(let i of e){let o=i.indexOf("=");if(o===-1)continue;let s=i.slice(0,o).trim(),a=i.slice(o+1).trim();if(s==="t")t=Number.parseInt(a,10);else if(s==="v1")n.push(a)}if(!Number.isFinite(t)||n.length===0)throw new c("Stripe-Signature header is malformed","malformed");return{timestamp:t,signatures:n}}function X(r,e,t,n={}){if(!r)throw new c("Stripe webhook secret is empty","missing");if(!e)throw new c("Stripe-Signature header is missing","missing");let{timestamp:i,signatures:o}=W(e),s=n.toleranceSeconds??300;if(Math.floor((n.now??Date.now())/1000)-i>s)throw new c("Stripe webhook timestamp is outside tolerance window","expired");let f=typeof t==="string"?t:t.toString("utf8"),y=x(r,`${i}.${f}`,"sha256");for(let _ of o){let A;try{A=w(_)}catch{continue}if(b(y,A))return!0}throw new c("Stripe webhook signature did not match","mismatch")}function L(r,e,t){if(!r)throw new c("GitHub webhook secret is empty","missing");if(!e)throw new c("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!e.startsWith(n))throw new c("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let i=w(e.slice(n.length)),o=x(r,t,"sha256");if(!b(o,i))throw new c("GitHub webhook signature did not match","mismatch");return!0}function j(r,e,t,n="sha256"){if(!r)throw new c("HMAC secret is empty","missing");if(!t)throw new c("Signature header is missing","missing");let i=t.indexOf("="),o=i!==-1&&/^[a-z0-9]+$/i.test(t.slice(0,i))?t.slice(i+1):t,s=w(o),a=x(r,e,n);if(!b(a,s))throw new c("HMAC signature did not match","mismatch");return!0}function xr(r,e,t,n,i={}){switch(r){case"stripe":return X(e,t,n,i);case"github":return L(e,t,n);case"generic":return j(e,n,t,i.algorithm??"sha256");default:throw new c(`Unknown webhook provider: ${String(r)}`,"malformed")}}export{c as InvalidWebhookSignature,ir as _resetCryptCacheForTests,G as argon2Encode,cr as base64Encode,gr as base64Verify,F as bcryptEncode,ur as check,nr as decrypt,M as detectAlgorithm,er as encrypt,lr as generateAppKey,ur as hashCheck,M as hashDetectAlgorithm,Y as hashInfo,fr as hashMake,mr as hashNeedsRehash,Y as info,tr as legacyEncrypt,fr as make,fr as makeHash,pr as md5Encode,mr as needsRehash,hr as timingSafeEqualString,L as verifyGithub,ur as verifyHash,j as verifyHmac,X as verifyStripe,xr as verifyWebhook};
2
+ var T=import.meta.require;import{Buffer as d}from"buffer";import{config as E}from"@stacksjs/config";import{decrypt as A,encrypt as Z}from"ts-security-crypto";function S(r){return r.startsWith("base64:")?r.slice(7):r}var k=1,B=600000,N=32,m=16,g=12,U=64,u=new Map;function V(r,e,t){return`${t}:${d.from(e).toString("base64")}:${r}`}async function C(r,e,t){let n=V(r,e,t),i=u.get(n);if(i)return i.hits++,u.delete(n),u.set(n,i),i.key;let o=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:t,hash:"SHA-256"},o,{name:"AES-GCM",length:N*8},!1,["encrypt","decrypt"]);if(u.set(n,{key:s,hits:1}),u.size>U){let a=u.keys().next().value;if(a!==void 0)u.delete(a)}return s}async function D(r,e){let t=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(g)),i=await C(e,t,B),o=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},i,new TextEncoder().encode(r)),s=new Uint8Array(o),a=new Uint8Array(1+m+g+s.byteLength);return a[0]=k,a.set(t,1),a.set(n,1+m),a.set(s,1+m+g),d.from(a).toString("base64")}async function O(r,e){let t=d.from(r,"base64");if(t.length<1+m+g+16)return null;if(t[0]!==k)return null;let n=new Uint8Array(t.subarray(1,1+m)),i=new Uint8Array(t.subarray(1+m,1+m+g)),o=new Uint8Array(t.subarray(1+m+g)),s=await C(e,n,B);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:i},s,o);return new TextDecoder().decode(a)}catch{return null}}async function rr(r,e){if(!r&&r!=="")throw Error("encrypt() requires a string message");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");return await D(r,S(t))}async function tr(r,e){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");let n=S(t),i=await O(r,n);if(i!==null)return i;try{return await A(r,n)}catch(o){if(n!==t)try{return await A(r,t)}catch{}throw o}}function er(){u.clear()}import{timingSafeEqual as P}from"crypto";import{base64Encode as sr,hashPassword as v,verifyPassword as K}from"ts-security-crypto";var p=null;function h(){if(p)return p;try{let{hashing:r}=T("@stacksjs/config");return p=r||{},p}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),p={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},p}}function M(r){if(!r||typeof r!=="string")return"unknown";if(r.startsWith("$2a$")||r.startsWith("$2b$")||r.startsWith("$2y$"))return"bcrypt";if(r.startsWith("$argon2id$"))return"argon2id";if(r.startsWith("$argon2i$"))return"argon2i";if(r.startsWith("$argon2d$"))return"argon2d";return"unknown"}function q(r){let e=M(r),t={};if(e==="bcrypt"){let n=r.match(/^\$2[aby]\$(\d{2})\$/);if(n&&n[1]!==void 0)t.rounds=Number.parseInt(n[1],10)}else if(e==="argon2id"||e==="argon2i"||e==="argon2d"){let n=r.match(/v=(\d+)/),i=r.match(/m=(\d+)/),o=r.match(/t=(\d+)/),s=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)t.version=Number.parseInt(n[1],10);if(i&&i[1]!==void 0)t.memory=Number.parseInt(i[1],10);if(o&&o[1]!==void 0)t.rounds=Number.parseInt(o[1],10);if(s&&s[1]!==void 0)t.parallelism=Number.parseInt(s[1],10)}return{algorithm:e,options:t}}function ar(r,e){let t=q(r),n=h(),i=e?.algorithm||n.driver||"bcrypt",o=(f)=>{if(f==="argon2")return"argon2id";return f},s=o(t.algorithm),a=o(i);if(s!==a)return!0;if(s==="bcrypt"){let f=e?.rounds||n.bcrypt?.rounds||12;if(t.options.rounds!==f)return!0}if(s.startsWith("argon2")){let f=e?.memory||n.argon2?.memory||65536,y=e?.time||n.argon2?.time||2;if(t.options.memory!==f||t.options.rounds!==y)return!0}return!1}async function cr(r,e){let t=h(),n=e?.algorithm||t.driver||"bcrypt";if(n==="bcrypt")return await R(r,e?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await Y(r,{type:n==="argon2"?"argon2id":n,memory:e?.memory,time:e?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function mr(r,e){if(!r||!e)return!1;if(M(e)==="unknown")try{return await K(r,e)}catch{return!1}return await K(r,e)}async function R(r,e){let t=h(),n=e||t.bcrypt?.rounds||12;return await v(r,{algorithm:"bcrypt",cost:n})}async function Y(r,e){let t=h(),n=e?.type||"argon2id",i=e?.memory||t.argon2?.memory||65536,o=e?.time||t.argon2?.time||2;return await v(r,{algorithm:n,memoryCost:i,timeCost:o})}function fr(r,e){let t=Buffer.from(r),n=Buffer.from(e);if(t.length!==n.length)return P(t,t),!1;return P(t,n)}import{generateKey as F}from"ts-security-crypto";function pr(){return F(32)}import{createHmac as G,timingSafeEqual as H}from"crypto";class c extends Error{reason;constructor(r,e="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=e}}function l(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new c("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function w(r,e){if(r.length!==e.length)return H(r,r),!1;return H(r,e)}function b(r,e,t){return G(t,r).update(e).digest()}function z(r){let e=r.split(","),t=Number.NaN,n=[];for(let i of e){let o=i.indexOf("=");if(o===-1)continue;let s=i.slice(0,o).trim(),a=i.slice(o+1).trim();if(s==="t")t=Number.parseInt(a,10);else if(s==="v1")n.push(a)}if(!Number.isFinite(t)||n.length===0)throw new c("Stripe-Signature header is malformed","malformed");return{timestamp:t,signatures:n}}function I(r,e,t,n={}){if(!r)throw new c("Stripe webhook secret is empty","missing");if(!e)throw new c("Stripe-Signature header is missing","missing");let{timestamp:i,signatures:o}=z(e),s=n.toleranceSeconds??300;if(Math.floor((n.now??Date.now())/1000)-i>s)throw new c("Stripe webhook timestamp is outside tolerance window","expired");let f=typeof t==="string"?t:t.toString("utf8"),y=b(r,`${i}.${f}`,"sha256");for(let _ of o){let x;try{x=l(_)}catch{continue}if(w(y,x))return!0}throw new c("Stripe webhook signature did not match","mismatch")}function W(r,e,t){if(!r)throw new c("GitHub webhook secret is empty","missing");if(!e)throw new c("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!e.startsWith(n))throw new c("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let i=l(e.slice(n.length)),o=b(r,t,"sha256");if(!w(o,i))throw new c("GitHub webhook signature did not match","mismatch");return!0}function X(r,e,t,n="sha256"){if(!r)throw new c("HMAC secret is empty","missing");if(!t)throw new c("Signature header is missing","missing");let i=t.indexOf("="),o=i!==-1&&/^[a-z0-9]+$/i.test(t.slice(0,i))?t.slice(i+1):t,s=l(o),a=b(r,e,n);if(!w(a,s))throw new c("HMAC signature did not match","mismatch");return!0}function dr(r,e,t,n,i={}){switch(r){case"stripe":return I(e,t,n,i);case"github":return W(e,t,n);case"generic":return X(e,n,t,i.algorithm??"sha256");default:throw new c(`Unknown webhook provider: ${String(r)}`,"malformed")}}export{c as InvalidWebhookSignature,er as _resetCryptCacheForTests,Y as argon2Encode,sr as base64Encode,R as bcryptEncode,mr as check,tr as decrypt,M as detectAlgorithm,rr as encrypt,pr as generateAppKey,mr as hashCheck,M as hashDetectAlgorithm,q as hashInfo,cr as hashMake,ar as hashNeedsRehash,q as info,Z as legacyEncrypt,cr as make,cr as makeHash,ar as needsRehash,fr as timingSafeEqualString,W as verifyGithub,mr as verifyHash,X as verifyHmac,I as verifyStripe,dr as verifyWebhook};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/security",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.16",
5
+ "version": "0.75.18",
6
6
  "description": "The Stacks framework security.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -57,13 +57,13 @@
57
57
  "prepublishOnly": "bun run build"
58
58
  },
59
59
  "dependencies": {
60
- "@stacksjs/config": "0.75.16",
60
+ "@stacksjs/config": "0.75.18",
61
61
  "ts-security-crypto": "0.0.2"
62
62
  },
63
63
  "devDependencies": {
64
- "@stacksjs/env": "0.75.16",
65
- "@stacksjs/types": "0.75.16",
66
- "@stacksjs/validation": "0.75.16",
64
+ "@stacksjs/env": "0.75.18",
65
+ "@stacksjs/types": "0.75.18",
66
+ "@stacksjs/validation": "0.75.18",
67
67
  "better-dx": "^0.2.24"
68
68
  }
69
69
  }