@stacksjs/auth 0.70.45 → 0.70.54

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2023 Open Web Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -1,157 +1,165 @@
1
1
  // @bun
2
- var C4=Object.defineProperty;var j4=($)=>$;function M4($,Z){this[$]=j4.bind(null,Z)}var z4=($,Z)=>{for(var Q in Z)C4($,Q,{get:Z[Q],enumerable:!0,configurable:!0,set:M4.bind(Z,Q)})};var H4=($,Z)=>()=>($&&(Z=$($=0)),Z);var U4=import.meta.require;var r0={};z4(r0,{policy:()=>X0,none:()=>l0,inspect:()=>n,hasPolicy:()=>i0,has:()=>n0,getPolicyFor:()=>d0,flush:()=>o0,denies:()=>c0,define:()=>h0,default:()=>J5,cannot:()=>F0,can:()=>L0,before:()=>p0,authorize:()=>V0,any:()=>Y0,allows:()=>u0,all:()=>K0,after:()=>m0,abilities:()=>t0,Gate:()=>a0,AuthorizationResponse:()=>z,AuthorizationException:()=>Q0});class z{isAllowed;message;code;constructor($,Z,Q){this.isAllowed=$,this.message=Z,this.code=Q}static allow($){return new z(!0,$)}static deny($,Z){return new z(!1,$||"This action is unauthorized.",Z)}allowed(){return this.isAllowed}denied(){return!this.isAllowed}authorize(){if(!this.isAllowed)throw new Q0(this.message||"This action is unauthorized.",this.code)}}function h0($,Z){H.gates.set($,Z)}function X0($,Z){let Q=typeof $==="string"?$:$.name;H.policies.set(Q,Z)}function p0($){H.beforeCallbacks.push($)}function m0($){H.afterCallbacks.push($)}async function u0($,Z,...Q){return i($,Z,...Q)}async function c0($,Z,...Q){return!await i($,Z,...Q)}async function L0($,Z,...Q){return i($,Z,...Q)}async function F0($,Z,...Q){return!await i($,Z,...Q)}async function Y0($,Z,...Q){for(let X of $)if(await i(X,Z,...Q))return!0;return!1}async function K0($,Z,...Q){for(let X of $)if(!await i(X,Z,...Q))return!1;return!0}async function l0($,Z,...Q){return!await Y0($,Z,...Q)}async function V0($,Z,...Q){let X=await n($,Z,...Q);if(!X.isAllowed)throw new Q0(X.message,X.code);return X}async function n($,Z,...Q){for(let _ of H.beforeCallbacks){let W=await _(Z,$,Q);if(W===!0)return z.allow();if(W===!1)return z.deny()}let X=Q[0];if(X&&typeof X==="object"){let _=X.constructor?.name,W=H.policies.get(_);if(W){let J=new W;if(J.before){let K=await J.before(Z,$);if(K===!0)return z.allow();if(K===!1)return z.deny()}let G=J[$];if(G){let K=await G.call(J,Z,...Q);return k0(K??!1)}}}let Y=H.gates.get($);if(Y){let _=await Y(Z,...Q),W=k0(_);for(let J of H.afterCallbacks){let G=await J(Z,$,W.isAllowed,Q);if(typeof G==="boolean")return G?z.allow():z.deny()}return W}return z.deny(`No gate or policy defined for ability: ${$}`)}async function i($,Z,...Q){return(await n($,Z,...Q)).isAllowed}function k0($){if($ instanceof z)return $;if(typeof $!=="boolean")throw TypeError(`[gate] Policy must return boolean or AuthorizationResponse; got ${typeof $}. If you returned a model/value by mistake, return \`true\`/\`false\` instead.`);return $?z.allow():z.deny()}function d0($){if(!$||typeof $!=="object")return null;let Z=$.constructor?.name,Q=H.policies.get(Z);if(Q)return new Q;return null}function n0($){return H.gates.has($)}function i0($){let Z=typeof $==="string"?$:$.name;return H.policies.has(Z)}function t0(){return Array.from(H.gates.keys())}function o0(){H.gates.clear(),H.policies.clear(),H.beforeCallbacks=[],H.afterCallbacks=[]}var Q0,H,a0,J5;var t=H4(()=>{Q0=class Q0 extends Error{code;status;constructor($="This action is unauthorized.",Z,Q=403){super($);this.code=Z;this.status=Q;this.name="AuthorizationException"}};H={gates:new Map,policies:new Map,beforeCallbacks:[],afterCallbacks:[]};a0={define:h0,policy:X0,before:p0,after:m0,allows:u0,denies:c0,can:L0,cannot:F0,any:Y0,all:K0,none:l0,authorize:V0,inspect:n,has:n0,hasPolicy:i0,abilities:t0,getPolicyFor:d0,flush:o0,AuthorizationResponse:z,AuthorizationException:Q0},J5=a0});import{config as U}from"@stacksjs/config";import{db as O}from"@stacksjs/database";import{HttpError as J0}from"@stacksjs/error-handling";import{formatDate as P,User as $0}from"@stacksjs/orm";import{request as M0}from"@stacksjs/router";import{Buffer as A}from"buffer";import{createHash as u4,randomBytes as c4,timingSafeEqual as z0}from"crypto";import{decrypt as c,encrypt as N0,verifyHash as H0}from"@stacksjs/security";import{log as y0}from"@stacksjs/logging";import{HttpError as v4}from"@stacksjs/error-handling";var E4=5,S4=900000,x4=1e4,P4=300000,w=new Map,A0=Date.now();function A4(){let $=Date.now(),Z=$-A0>=P4,Q=w.size>=x4;if(!Z&&!Q)return;A0=$;for(let[X,Y]of w)if(Y.lockedUntil>0&&Y.lockedUntil<=$)w.delete(X);else if(Y.lockedUntil===0&&Y.attempts===0)w.delete(X)}class r{static isRateLimited($){$=$.toLowerCase(),A4();let Z=Date.now(),Q=w.get($);if(!Q)return!1;if(Q.lockedUntil>0&&Q.lockedUntil<=Z)return w.delete($),!1;if(Q.lockedUntil>0)return!0;return!1}static recordFailedAttempt($){$=$.toLowerCase();let Z=Date.now(),Q=w.get($)||{attempts:0,lockedUntil:0};if(Q.attempts++,Q.attempts>=E4)Q.lockedUntil=Z+S4,Q.attempts=0;w.set($,Q)}static resetAttempts($){w.delete($.toLowerCase())}static validateAttempt($){if(this.isRateLimited($))throw new v4(429,"Too many login attempts. Please try again later.")}}import{Buffer as R0}from"buffer";import{createHmac as R4,randomBytes as w4}from"crypto";import N4 from"process";class _0{static generateJWT($,Z=3600){let Q=N4.env.APP_KEY;if(!Q)throw Error("APP_KEY is not set. JWT tokens cannot be generated without a secure application key.");let X={alg:"HS256",typ:"JWT"},Y=Math.floor(Date.now()/1000),_={sub:$,iat:Y,exp:Y+Z,jti:w4(16).toString("hex")},W=R0.from(JSON.stringify(X)).toString("base64url"),J=R0.from(JSON.stringify(_)).toString("base64url"),G=R4("sha256",Q).update(`${W}.${J}`).digest("base64url");return`${W}.${J}.${G}`}}import{createHash as y4,randomBytes as I4}from"crypto";import{db as F}from"@stacksjs/database";import{HttpError as C0}from"@stacksjs/error-handling";import{getCurrentRequest as T4}from"@stacksjs/router";import{env as f4}from"@stacksjs/env";import{sqlHelpers as b4}from"@stacksjs/database";var g4=f4.DB_CONNECTION||"sqlite",w0=b4(g4),{isPostgres:m,isMysql:_6,now:M,boolTrue:v,boolFalse:e}=w0;function C($){return w0.param($)}function N($){return y4("sha256").update($).digest("hex")}function s($=40){return I4($).toString("hex")}async function J6($){return(await F.unsafe(`
2
+ var X$=Object.defineProperty;var Y$=($)=>$;function K$($,W){this[$]=Y$.bind(null,W)}var e0=($,W)=>{for(var Z in W)X$($,Z,{get:W[Z],enumerable:!0,configurable:!0,set:K$.bind(W,Z)})};var $1=($,W)=>()=>($&&(W=$($=0)),W);var D0=import.meta.require;var K1={};e0(K1,{validateRefreshToken:()=>E$,tokens:()=>M$,tokenCant:()=>q$,tokenCanAny:()=>P$,tokenCanAll:()=>C$,tokenCan:()=>X1,tokenAbilities:()=>v$,token:()=>U$,revokeTokenById:()=>y$,revokeToken:()=>N$,revokeRefreshToken:()=>x$,revokeOtherTokens:()=>T$,revokeClient:()=>g$,revokeAllTokens:()=>j0,revokeAllRefreshTokens:()=>Y1,refreshToken:()=>S$,parseScopes:()=>f,isIssuedBeforePasswordChange:()=>d,getPasswordChangedAt:()=>s,findToken:()=>G1,findClient:()=>b$,deleteRevokedTokens:()=>w$,deleteRevokedRefreshTokens:()=>A$,deleteExpiredTokens:()=>f$,deleteExpiredRefreshTokens:()=>R$,currentAccessToken:()=>n,createToken:()=>f0,createClient:()=>k$,clients:()=>I$});import{createHash as O$,randomBytes as _$}from"crypto";import{db as B}from"@stacksjs/database";import{HttpError as X0}from"@stacksjs/error-handling";import{getCurrentRequest as z$}from"@stacksjs/router";import{env as D$}from"@stacksjs/env";import{sqlHelpers as j$}from"@stacksjs/database";function z($){return J1.param($)}function h($){return O$("sha256").update($).digest("hex")}function Q1($){let W=$.indexOf(":"),Z=W===-1?$:$.substring(0,W);return h(Z)}function Y0($=40){return _$($).toString("hex")}async function s($,W=B){if($===null||$===void 0)return null;try{let J=(await W.unsafe(`
3
+ SELECT password_changed_at FROM users WHERE id = ${z(1)} LIMIT 1
4
+ `,[$]))[0]?.password_changed_at;if(J===null||J===void 0)return null;let Q=new Date(String(J));return Number.isNaN(Q.getTime())?null:Q}catch{return null}}function d($,W){if(!W)return!1;if($===null||$===void 0)return!1;let Z=new Date(String($));if(Number.isNaN(Z.getTime()))return!1;return Z.getTime()<W.getTime()}async function M$($){return(await B.unsafe(`
3
5
  SELECT t.*, c.provider as client_provider
4
6
  FROM oauth_access_tokens t
5
7
  LEFT JOIN oauth_clients c ON t.oauth_client_id = c.id
6
- WHERE t.user_id = ${C(1)}
7
- AND t.revoked = ${e}
8
+ WHERE t.user_id = ${z(1)}
9
+ AND t.revoked = ${K0}
8
10
  ORDER BY t.created_at DESC
9
- `,[$])).map((Q)=>({id:Q.id,userId:Q.user_id,clientId:Q.oauth_client_id,name:Q.name||"access-token",scopes:y(Q.scopes),revoked:!!Q.revoked,expiresAt:Q.expires_at?new Date(Q.expires_at):null,createdAt:new Date(Q.created_at),updatedAt:Q.updated_at?new Date(Q.updated_at):new Date}))}async function k4($){let Z=N($),X=(await F.unsafe(`
11
+ `,[$])).map((Z)=>({id:Z.id,userId:Z.user_id,clientId:Z.oauth_client_id,name:Z.name||"access-token",scopes:f(Z.scopes),revoked:!!Z.revoked,expiresAt:Z.expires_at?new Date(Z.expires_at):null,createdAt:new Date(Z.created_at),updatedAt:Z.updated_at?new Date(Z.updated_at):new Date}))}async function G1($){let W=Q1($),J=(await B.unsafe(`
10
12
  SELECT * FROM oauth_access_tokens
11
- WHERE token = ${C(1)}
12
- AND revoked = ${e}
13
- AND (expires_at IS NULL OR expires_at > ${M})
13
+ WHERE token = ${z(1)}
14
+ AND revoked = ${K0}
15
+ AND (expires_at IS NULL OR expires_at > ${U})
14
16
  LIMIT 1
15
- `,[Z]))[0];if(!X)return null;return{id:X.id,userId:X.user_id,clientId:X.oauth_client_id,name:X.name||"access-token",scopes:y(X.scopes),revoked:!!X.revoked,expiresAt:X.expires_at?new Date(X.expires_at):null,createdAt:new Date(X.created_at),updatedAt:X.updated_at?new Date(X.updated_at):new Date}}async function u(){let $=T4();if(!$)return null;let Z=$._currentAccessToken;if(Z)return Z;let Q=$.bearerToken?.();if(!Q)return null;let X=await k4(Q);if(X)$._currentAccessToken=X;return X}var G6=u;async function h4($){let Z=await u();if(!Z)return!1;if(Z.scopes.includes("*"))return!0;return Z.scopes.includes($)}async function L6($){return!await h4($)}async function F6($){let Z=await u();if(!Z)return!1;if(Z.scopes.includes("*"))return!0;return $.every((Q)=>Z.scopes.includes(Q))}async function K6($){let Z=await u();if(!Z)return!1;if(Z.scopes.includes("*"))return!0;return $.some((Q)=>Z.scopes.includes(Q))}async function V6(){return(await u())?.scopes||[]}async function B6($,Z="access-token",Q=["*"],X={}){let{expiresInMinutes:Y=60,withRefreshToken:_=!0,refreshExpiresInDays:W=30}=X,G=(await F.unsafe(`
17
+ `,[W]))[0];if(!J)return null;if(d(J.created_at,await s(J.user_id)))return null;return{id:J.id,userId:J.user_id,clientId:J.oauth_client_id,name:J.name||"access-token",scopes:f(J.scopes),revoked:!!J.revoked,expiresAt:J.expires_at?new Date(J.expires_at):null,createdAt:new Date(J.created_at),updatedAt:J.updated_at?new Date(J.updated_at):new Date}}async function n(){let $=z$();if(!$)return null;let W=$._currentAccessToken;if(W)return W;let Z=$.bearerToken?.();if(!Z)return null;let J=await G1(Z);if(J)$._currentAccessToken=J;return J}async function X1($){let W=await n();if(!W)return!1;if(W.scopes.includes("*"))return!0;return W.scopes.includes($)}async function q$($){return!await X1($)}async function C$($){let W=await n();if(!W)return!1;if(W.scopes.includes("*"))return!0;return $.every((Z)=>W.scopes.includes(Z))}async function P$($){let W=await n();if(!W)return!1;if(W.scopes.includes("*"))return!0;return $.some((Z)=>W.scopes.includes(Z))}async function v$(){return(await n())?.scopes||[]}async function f0($,W="access-token",Z=["*"],J={}){let{expiresInMinutes:Q=60,withRefreshToken:G=!0,refreshExpiresInDays:X=30}=J,Y=(await B.unsafe(`
16
18
  SELECT id FROM oauth_clients WHERE personal_access_client = ${v} LIMIT 1
17
- `))[0];if(!G)throw new C0(500,"No personal access client found. Run ./buddy auth:setup first.");let K=s(40),B=N(K),V=new Date;if(V.setMinutes(V.getMinutes()+Y),m)await F.unsafe(`
19
+ `))[0];if(!Y)throw new X0(500,"No personal access client found. Run ./buddy auth:setup first.");let L=Y0(40),O=h(L),_=new Date;if(_.setMinutes(_.getMinutes()+Q),r)await B.unsafe(`
18
20
  INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
19
21
  VALUES ($1, $2, $3, $4, $5, false, $6, NOW(), NOW())
20
- `,[$,G.id,B,Z,JSON.stringify(Q),V.toISOString()]);else await F.unsafe(`
22
+ `,[$,Y.id,O,W,JSON.stringify(Z),_.toISOString()]);else await B.unsafe(`
21
23
  INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
22
- VALUES (?, ?, ?, ?, ?, 0, ?, ${M}, ${M})
23
- `,[$,G.id,B,Z,JSON.stringify(Q),V.toISOString()]);let j=(await F.unsafe(`
24
- SELECT * FROM oauth_access_tokens WHERE token = ${C(1)} LIMIT 1
25
- `,[B]))[0];if(!j)throw new C0(500,"Failed to create access token \u2014 inserted row not found");let x={id:j.id,userId:j.user_id,clientId:j.oauth_client_id,name:j.name,scopes:y(j.scopes),revoked:!1,expiresAt:V,createdAt:new Date(j.created_at),updatedAt:new Date(j.updated_at)},E;if(_){E=s(40);let T=N(E),k=new Date;if(k.setDate(k.getDate()+W),m)await F.unsafe(`
24
+ VALUES (?, ?, ?, ?, ?, 0, ?, ${U}, ${U})
25
+ `,[$,Y.id,O,W,JSON.stringify(Z),_.toISOString()]);let F=(await B.unsafe(`
26
+ SELECT * FROM oauth_access_tokens WHERE token = ${z(1)} LIMIT 1
27
+ `,[O]))[0];if(!F)throw new X0(500,"Failed to create access token \u2014 inserted row not found");let H={id:F.id,userId:F.user_id,clientId:F.oauth_client_id,name:F.name,scopes:f(F.scopes),revoked:!1,expiresAt:_,createdAt:new Date(F.created_at),updatedAt:new Date(F.updated_at)},P;if(G){P=Y0(40);let T=h(P),A=new Date;if(A.setDate(A.getDate()+X),r)await B.unsafe(`
26
28
  INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
27
29
  VALUES ($1, $2, false, $3, NOW())
28
- `,[x.id,T,k.toISOString()]);else await F.unsafe(`
30
+ `,[H.id,T,A.toISOString()]);else await B.unsafe(`
29
31
  INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
30
- VALUES (?, ?, 0, ?, ${M})
31
- `,[x.id,T,k.toISOString()])}return{accessToken:x,plainTextToken:K,refreshToken:E,expiresIn:Y*60}}async function O6($,Z={}){let{expiresInMinutes:Q=60,refreshExpiresInDays:X=30}=Z,Y=N($),W=(await F.unsafe(`
32
- SELECT r.*, t.user_id, t.oauth_client_id, t.name, t.scopes
33
- FROM oauth_refresh_tokens r
34
- JOIN oauth_access_tokens t ON r.access_token_id = t.id
35
- WHERE r.token = ${C(1)}
36
- AND r.revoked = ${e}
37
- AND (r.expires_at IS NULL OR r.expires_at > ${M})
38
- LIMIT 1
39
- `,[Y]))[0];if(!W)throw new C0(401,"Invalid or expired refresh token");await F.unsafe(`
40
- UPDATE oauth_refresh_tokens
41
- SET revoked = ${v}
42
- WHERE id = ${C(1)}
43
- `,[W.id]);let J=s(40),G=N(J),K=new Date;if(K.setMinutes(K.getMinutes()+Q),m)await F.unsafe(`
44
- INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
45
- VALUES ($1, $2, $3, $4, $5, false, $6, NOW(), NOW())
46
- `,[W.user_id,W.oauth_client_id,G,W.name,W.scopes,K.toISOString()]);else await F.unsafe(`
47
- INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
48
- VALUES (?, ?, ?, ?, ?, 0, ?, ${M}, ${M})
49
- `,[W.user_id,W.oauth_client_id,G,W.name,W.scopes,K.toISOString()]);let V=(await F.unsafe(`
50
- SELECT * FROM oauth_access_tokens WHERE token = ${C(1)} LIMIT 1
51
- `,[G]))[0],q={id:V.id,userId:V.user_id,clientId:V.oauth_client_id,name:V.name,scopes:y(V.scopes),revoked:!1,expiresAt:K,createdAt:new Date(V.created_at),updatedAt:new Date(V.updated_at)},j=s(40),x=N(j),E=new Date;if(E.setDate(E.getDate()+X),m)await F.unsafe(`
52
- INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
53
- VALUES ($1, $2, false, $3, NOW())
54
- `,[q.id,x,E.toISOString()]);else await F.unsafe(`
55
- INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
56
- VALUES (?, ?, 0, ?, ${M})
57
- `,[q.id,x,E.toISOString()]);return{accessToken:q,plainTextToken:J,refreshToken:j,expiresIn:Q*60}}async function D6($){let Z=N($);return(await F.unsafe(`
32
+ VALUES (?, ?, 0, ?, ${U})
33
+ `,[H.id,T,A.toISOString()])}return{accessToken:H,plainTextToken:L,refreshToken:P,expiresIn:Q*60}}async function S$($,W={}){let{expiresInMinutes:Z=60,refreshExpiresInDays:J=30}=W,Q=h($);return await B.transaction(async(G)=>{let X=G,Y=(await X.unsafe(`
34
+ SELECT r.*, t.user_id, t.oauth_client_id, t.name, t.scopes
35
+ FROM oauth_refresh_tokens r
36
+ JOIN oauth_access_tokens t ON r.access_token_id = t.id
37
+ WHERE r.token = ${z(1)}
38
+ AND r.revoked = ${K0}
39
+ AND (r.expires_at IS NULL OR r.expires_at > ${U})
40
+ LIMIT 1
41
+ `,[Q]))[0];if(!Y)throw new X0(401,"Invalid or expired refresh token");if(d(Y.created_at,await s(Y.user_id,X)))throw new X0(401,"Invalid or expired refresh token");await X.unsafe(`
42
+ UPDATE oauth_refresh_tokens
43
+ SET revoked = ${v}
44
+ WHERE id = ${z(1)}
45
+ `,[Y.id]),await X.unsafe(`
46
+ UPDATE oauth_access_tokens
47
+ SET revoked = ${v}
48
+ WHERE id = ${z(1)}
49
+ `,[Y.access_token_id]);let L=Y0(40),O=h(L),_=new Date;if(_.setMinutes(_.getMinutes()+Z),r)await X.unsafe(`
50
+ INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
51
+ VALUES ($1, $2, $3, $4, $5, false, $6, NOW(), NOW())
52
+ `,[Y.user_id,Y.oauth_client_id,O,Y.name,Y.scopes,_.toISOString()]);else await X.unsafe(`
53
+ INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
54
+ VALUES (?, ?, ?, ?, ?, 0, ?, ${U}, ${U})
55
+ `,[Y.user_id,Y.oauth_client_id,O,Y.name,Y.scopes,_.toISOString()]);let F=(await X.unsafe(`
56
+ SELECT * FROM oauth_access_tokens WHERE token = ${z(1)} LIMIT 1
57
+ `,[O]))[0],H={id:F.id,userId:F.user_id,clientId:F.oauth_client_id,name:F.name,scopes:f(F.scopes),revoked:!1,expiresAt:_,createdAt:new Date(F.created_at),updatedAt:new Date(F.updated_at)};if(d(F.created_at,await s(Y.user_id,X)))throw new X0(401,"Invalid or expired refresh token");let P=Y0(40),T=h(P),A=new Date;if(A.setDate(A.getDate()+J),r)await X.unsafe(`
58
+ INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
59
+ VALUES ($1, $2, false, $3, NOW())
60
+ `,[H.id,T,A.toISOString()]);else await X.unsafe(`
61
+ INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
62
+ VALUES (?, ?, 0, ?, ${U})
63
+ `,[H.id,T,A.toISOString()]);return{accessToken:H,plainTextToken:L,refreshToken:P,expiresIn:Z*60}})}async function E$($){let W=h($);return(await B.unsafe(`
58
64
  SELECT id FROM oauth_refresh_tokens
59
- WHERE token = ${C(1)}
60
- AND revoked = ${e}
61
- AND (expires_at IS NULL OR expires_at > ${M})
65
+ WHERE token = ${z(1)}
66
+ AND revoked = ${K0}
67
+ AND (expires_at IS NULL OR expires_at > ${U})
62
68
  LIMIT 1
63
- `,[Z])).length>0}async function q6($){let Z=N($);await F.unsafe(`
69
+ `,[W])).length>0}async function x$($){let W=h($);await B.unsafe(`
64
70
  UPDATE oauth_refresh_tokens
65
71
  SET revoked = ${v}
66
- WHERE token = ${C(1)}
67
- `,[Z])}async function p4($){await F.unsafe(`
72
+ WHERE token = ${z(1)}
73
+ `,[W])}async function Y1($){await B.unsafe(`
68
74
  UPDATE oauth_refresh_tokens
69
75
  SET revoked = ${v}
70
76
  WHERE access_token_id IN (
71
- SELECT id FROM oauth_access_tokens WHERE user_id = ${C(1)}
77
+ SELECT id FROM oauth_access_tokens WHERE user_id = ${z(1)}
72
78
  )
73
- `,[$])}async function C6(){let $=await F.unsafe(`
79
+ `,[$])}async function R$(){let $=await B.unsafe(`
74
80
  DELETE FROM oauth_refresh_tokens
75
- WHERE expires_at < ${M}
76
- `);return $?.changes||$?.rowCount||0}async function j6($=7){let Z=new Date;Z.setDate(Z.getDate()-$);let Q=await F.unsafe(`
81
+ WHERE expires_at < ${U}
82
+ `);return $?.changes||$?.rowCount||0}async function A$($=7){let W=new Date;W.setDate(W.getDate()-$);let Z=await B.unsafe(`
77
83
  DELETE FROM oauth_refresh_tokens
78
- WHERE revoked = ${v} AND created_at < ${C(1)}
79
- `,[Z.toISOString()]);return Q?.changes||Q?.rowCount||0}async function M6($){let Z=N($);await F.unsafe(`
84
+ WHERE revoked = ${v} AND created_at < ${z(1)}
85
+ `,[W.toISOString()]);return Z?.changes||Z?.rowCount||0}async function N$($){let W=Q1($);await B.unsafe(`
80
86
  UPDATE oauth_refresh_tokens
81
87
  SET revoked = ${v}
82
88
  WHERE access_token_id IN (
83
- SELECT id FROM oauth_access_tokens WHERE token = ${C(1)}
89
+ SELECT id FROM oauth_access_tokens WHERE token = ${z(1)}
84
90
  )
85
- `,[Z]),await F.unsafe(`
91
+ `,[W]),await B.unsafe(`
86
92
  UPDATE oauth_access_tokens
87
- SET revoked = ${v}, updated_at = ${M}
88
- WHERE token = ${C(1)}
89
- `,[Z])}async function z6($){await F.unsafe(`
93
+ SET revoked = ${v}, updated_at = ${U}
94
+ WHERE token = ${z(1)}
95
+ `,[W])}async function y$($){await B.unsafe(`
90
96
  UPDATE oauth_refresh_tokens
91
97
  SET revoked = ${v}
92
- WHERE access_token_id = ${C(1)}
93
- `,[$]),await F.unsafe(`
98
+ WHERE access_token_id = ${z(1)}
99
+ `,[$]),await B.unsafe(`
94
100
  UPDATE oauth_access_tokens
95
- SET revoked = ${v}, updated_at = ${M}
96
- WHERE id = ${C(1)}
97
- `,[$])}async function m4($){await p4($),await F.unsafe(`
101
+ SET revoked = ${v}, updated_at = ${U}
102
+ WHERE id = ${z(1)}
103
+ `,[$])}async function j0($){await Y1($),await B.unsafe(`
98
104
  UPDATE oauth_access_tokens
99
- SET revoked = ${v}, updated_at = ${M}
100
- WHERE user_id = ${C(1)}
101
- `,[$])}async function H6($){let Z=await u();if(!Z)return m4($);if(m)await F.unsafe(`
105
+ SET revoked = ${v}, updated_at = ${U}
106
+ WHERE user_id = ${z(1)}
107
+ `,[$])}async function T$($){let W=await n();if(!W)return j0($);if(r)await B.unsafe(`
102
108
  UPDATE oauth_refresh_tokens
103
109
  SET revoked = true
104
110
  WHERE access_token_id IN (
105
111
  SELECT id FROM oauth_access_tokens WHERE user_id = $1 AND id != $2
106
112
  )
107
- `,[$,Z.id]),await F.unsafe(`
113
+ `,[$,W.id]),await B.unsafe(`
108
114
  UPDATE oauth_access_tokens
109
115
  SET revoked = true, updated_at = NOW()
110
116
  WHERE user_id = $1 AND id != $2
111
- `,[$,Z.id]);else await F.unsafe(`
117
+ `,[$,W.id]);else await B.unsafe(`
112
118
  UPDATE oauth_refresh_tokens
113
119
  SET revoked = 1
114
120
  WHERE access_token_id IN (
115
121
  SELECT id FROM oauth_access_tokens WHERE user_id = ? AND id != ?
116
122
  )
117
- `,[$,Z.id]),await F.unsafe(`
123
+ `,[$,W.id]),await B.unsafe(`
118
124
  UPDATE oauth_access_tokens
119
- SET revoked = 1, updated_at = ${M}
125
+ SET revoked = 1, updated_at = ${U}
120
126
  WHERE user_id = ? AND id != ?
121
- `,[$,Z.id])}async function U6(){await F.unsafe(`
127
+ `,[$,W.id])}async function f$(){await B.unsafe(`
122
128
  DELETE FROM oauth_refresh_tokens
123
129
  WHERE access_token_id IN (
124
- SELECT id FROM oauth_access_tokens WHERE expires_at < ${M}
130
+ SELECT id FROM oauth_access_tokens WHERE expires_at < ${U}
125
131
  )
126
- `);let $=await F.unsafe(`
132
+ `);let $=await B.unsafe(`
127
133
  DELETE FROM oauth_access_tokens
128
- WHERE expires_at < ${M}
129
- `);return $?.changes||$?.rowCount||0}async function v6($=7){let Z=new Date;Z.setDate(Z.getDate()-$),await F.unsafe(`
134
+ WHERE expires_at < ${U}
135
+ `);return $?.changes||$?.rowCount||0}async function w$($=7){let W=new Date;W.setDate(W.getDate()-$),await B.unsafe(`
130
136
  DELETE FROM oauth_refresh_tokens
131
137
  WHERE access_token_id IN (
132
- SELECT id FROM oauth_access_tokens WHERE revoked = ${v} AND updated_at < ${C(1)}
138
+ SELECT id FROM oauth_access_tokens WHERE revoked = ${v} AND updated_at < ${z(1)}
133
139
  )
134
- `,[Z.toISOString()]);let Q=await F.unsafe(`
140
+ `,[W.toISOString()]);let Z=await B.unsafe(`
135
141
  DELETE FROM oauth_access_tokens
136
- WHERE revoked = ${v} AND updated_at < ${C(1)}
137
- `,[Z.toISOString()]);return Q?.changes||Q?.rowCount||0}async function E6($){return(await F.unsafe(`
142
+ WHERE revoked = ${v} AND updated_at < ${z(1)}
143
+ `,[W.toISOString()]);return Z?.changes||Z?.rowCount||0}async function I$($){return(await B.unsafe(`
138
144
  SELECT * FROM oauth_clients
139
- WHERE user_id = ${C(1)} AND revoked = ${e}
145
+ WHERE user_id = ${z(1)} AND revoked = ${K0}
140
146
  ORDER BY created_at DESC
141
- `,[$])).map(j0)}async function S6($){let Q=(await F.unsafe(`
142
- SELECT * FROM oauth_clients WHERE id = ${C(1)} LIMIT 1
143
- `,[$]))[0];return Q?j0(Q):null}async function x6($){let Z=s(40);if(m)await F.unsafe(`
147
+ `,[$])).map(w0)}async function b$($){let Z=(await B.unsafe(`
148
+ SELECT * FROM oauth_clients WHERE id = ${z(1)} LIMIT 1
149
+ `,[$]))[0];return Z?w0(Z):null}async function k$($){let W=Y0(40);if(r)await B.unsafe(`
144
150
  INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
145
151
  VALUES ($1, $2, 'local', $3, $4, $5, false, NOW())
146
- `,[$.name,Z,$.redirect,$.personalAccessClient||!1,$.passwordClient||!1]);else await F.unsafe(`
152
+ `,[$.name,W,$.redirect,$.personalAccessClient||!1,$.passwordClient||!1]);else await B.unsafe(`
147
153
  INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
148
- VALUES (?, ?, 'local', ?, ?, ?, 0, ${M})
149
- `,[$.name,Z,$.redirect,$.personalAccessClient?1:0,$.passwordClient?1:0]);let Q=await F.unsafe(`
150
- SELECT * FROM oauth_clients WHERE secret = ${C(1)} LIMIT 1
151
- `,[Z]);return{client:j0(Q[0]),plainTextSecret:Z}}async function P6($){await F.unsafe(`
154
+ VALUES (?, ?, 'local', ?, ?, ?, 0, ${U})
155
+ `,[$.name,W,$.redirect,$.personalAccessClient?1:0,$.passwordClient?1:0]);let Z=await B.unsafe(`
156
+ SELECT * FROM oauth_clients WHERE secret = ${z(1)} LIMIT 1
157
+ `,[W]);return{client:w0(Z[0]),plainTextSecret:W}}async function g$($){await B.unsafe(`
152
158
  UPDATE oauth_clients
153
- SET revoked = ${v}, updated_at = ${M}
154
- WHERE id = ${C(1)}
155
- `,[$])}function y($){if(!$)return[];if(Array.isArray($))return $;try{let Z=JSON.parse($);return Array.isArray(Z)?Z:[]}catch{return[]}}function j0($){return{id:$.id,name:$.name,secret:$.secret,provider:$.provider,redirect:$.redirect,personalAccessClient:Boolean($.personal_access_client),passwordClient:Boolean($.password_client),revoked:Boolean($.revoked),createdAt:$.created_at?new Date($.created_at):new Date,updatedAt:$.updated_at?new Date($.updated_at):null}}function l($){return u4("sha256").update($).digest("hex")}class R{static authUser=void 0;static clientSecret=void 0;static currentToken=void 0;static getBearerToken(){let $=M0.bearerToken?.();if(!$){let Z=M0.headers?.get?.("authorization")||M0.headers?.get?.("Authorization");if(Z&&Z.startsWith("Bearer "))$=Z.substring(7)}return $||null}static parseToken($){let Z=$.indexOf(":");if(Z===-1)return null;let Q=$.substring(0,Z),X=$.substring(Z+1);if(!Q||!X)return null;return{plainToken:Q,encryptedId:X}}static async getClientSecret(){if(this.clientSecret)return this.clientSecret;let $=await this.getPersonalAccessClient();return this.clientSecret=$.secret,$.secret}static async getPersonalAccessClient(){try{let $=await O.selectFrom("oauth_clients").where("personal_access_client","=",!0).selectAll().executeTakeFirst();if(!$)throw new J0(500,"No personal access client found. Please run `./buddy auth:setup` first.");return $}catch($){if($ instanceof Error&&$.message.includes("does not exist"))throw new J0(500,"OAuth tables not found. Please run `./buddy auth:setup` first.");throw $}}static async validateClient($,Z){let Q=await O.selectFrom("oauth_clients").where("id","=",$).selectAll().executeTakeFirst();if(!Q?.secret)return!1;let X=A.from(String(Q.secret)),Y=A.from(Z);if(X.length!==Y.length)return!1;return z0(X,Y)}static async getTokenFromId($){let Z=await O.selectFrom("oauth_access_tokens").where("id","=",$).selectAll().executeTakeFirst();if(!Z)return null;let Q=Z;return{id:Q.id,userId:Q.user_id,clientId:Q.oauth_client_id,name:Q.name||"auth-token",scopes:y(Q.scopes),abilities:y(Q.scopes),expiresAt:Q.expires_at?new Date(String(Q.expires_at)):null,createdAt:Q.created_at?new Date(String(Q.created_at)):new Date,updatedAt:Q.updated_at?new Date(String(Q.updated_at)):new Date,revoked:!!Q.revoked}}static async attempt($){let Z=U.auth.username||"email",Q=U.auth.password||"password",X=$[Z];if(!X)return!1;if(r.isRateLimited(X))return!1;let Y=await $0.where("email","=",X).first(),_=$[Q]||"",W=Y?.password||"$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";if(await H0(_,W)&&Y)return r.resetAttempts(X),this.authUser=Y,!0;return r.recordFailedAttempt(X),!1}static async validate($){let Z=U.auth.username||"email",Q=U.auth.password||"password",X=$[Z];if(!X)return!1;let Y=await $0.where("email","=",X).first(),_=$[Q]||"",W=Y?.password||"$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";return await H0(_,W)&&!!Y}static async login($,Z){if(!await this.attempt($)||!this.authUser)return null;let{plainTextToken:X,refreshToken:Y,expiresIn:_}=await this.createTokenForUser(this.authUser,Z);return{user:this.authUser,token:X,refreshToken:Y,expiresIn:_}}static async loginUsingId($,Z){let Q=await $0.find($);if(!Q)return null;this.authUser=Q;let{plainTextToken:X,refreshToken:Y,expiresIn:_}=await this.createTokenForUser(Q,Z);return{user:Q,token:X,refreshToken:Y,expiresIn:_}}static async logout(){let $=this.getBearerToken();if($){let Z=this.parseToken($);if(Z){let Q=await this.getClientSecret(),X=await c(Z.encryptedId,Q).catch(()=>null);if(X)await O.updateTable("oauth_refresh_tokens").set({revoked:!0}).where("access_token_id","=",Number(X)).execute()}await this.revokeToken($)}this.authUser=void 0,this.currentToken=void 0}static async user(){if(this.authUser)return this.authUser;let $=this.getBearerToken();if(!$)return;let Z=await this.getUserFromToken($);if(Z)this.authUser=Z;return Z}static async check(){return await this.user()!==void 0}static async guest(){return!await this.check()}static async id(){return(await this.user())?.id}static setUser($){this.authUser=$}static async createTokenForUser($,Z){let Q=await this.getPersonalAccessClient(),X=await this.getClientSecret(),Y=Z?.name??U.auth.defaultTokenName??"auth-token",_=Z?.abilities??Z?.scopes??U.auth.defaultAbilities??["*"],W=Z?.expiresInMinutes!==void 0?Z.expiresInMinutes*60*1000:U.auth.tokenExpiry??3600000,J=Z?.expiresAt??new Date(Date.now()+W),G=Math.max(1,Math.floor((J.getTime()-Date.now())/1000)),K=_0.generateJWT($.id,G),B=l(K);y0.debug(`[auth] Creating token for user#${$.id}: ${Y}`),await O.insertInto("oauth_access_tokens").values({user_id:$.id,oauth_client_id:Q.id,name:Y,token:B,scopes:JSON.stringify(_),revoked:!1,expires_at:P(J)}).execute();let V=await O.selectFrom("oauth_access_tokens").where("token","=",B).selectAll().executeTakeFirst(),q=Number(V?.id);if(!q)throw new J0(500,"Failed to create token");let j=await N0(q.toString(),X),x=`${K}:${j}`,E={id:q,userId:$.id,clientId:Q.id,name:Y,scopes:_,abilities:_,expiresAt:J,createdAt:new Date,updatedAt:new Date,revoked:!1,plainTextToken:x},T;if(Z?.withRefreshToken!==!1){let k=Z?.refreshExpiresInDays??Math.max(1,Math.round((U.auth.refreshTokenExpiry??2592000000)/86400000));T=c4(40).toString("hex");let P0=l(T),q0=new Date(Date.now()+k*24*60*60*1000);await O.insertInto("oauth_refresh_tokens").values({access_token_id:q,token:P0,revoked:!1,expires_at:P(q0)}).execute()}return{accessToken:E,plainTextToken:x,refreshToken:T,expiresIn:G}}static async createToken($,Z=U.auth.defaultTokenName||"auth-token",Q=U.auth.defaultAbilities||["*"]){let{plainTextToken:X}=await this.createTokenForUser($,{name:Z,abilities:Q});return X}static async requestToken($,Z,Q){if(!await this.validateClient(Z,Q))throw new J0(401,"Invalid client credentials");if(!await this.attempt($)||!this.authUser)return null;return{token:await this.createToken(this.authUser,"user-auth-token")}}static async validateToken($){let Z=this.parseToken($);if(!Z)return!1;let{plainToken:Q,encryptedId:X}=Z,Y=await this.getClientSecret(),_=await c(X,Y);if(!_)return!1;let W=await O.selectFrom("oauth_access_tokens").where("id","=",Number(_)).selectAll().executeTakeFirst();if(!W)return!1;let J=l(Q),G=String(W.token);if(J.length!==G.length)return!1;if(!z0(A.from(J,"utf-8"),A.from(G,"utf-8")))return!1;if(y0.debug(`[auth] Token validated for token#${W.id}`),W.expires_at&&new Date(String(W.expires_at))<new Date)return await O.deleteFrom("oauth_access_tokens").where("id","=",W.id).execute(),!1;if(W.revoked)return!1;let B=U.auth.tokenRotation??24,V=W.updated_at?new Date(String(W.updated_at)):new Date,q=new Date;if((q.getTime()-V.getTime())/3600000>=B)await this.rotateToken($);else await O.updateTable("oauth_access_tokens").set({updated_at:P(q)}).where("id","=",W.id).execute();return!0}static async getUserFromToken($){let Z=this.parseToken($);if(!Z)return;let{plainToken:Q,encryptedId:X}=Z,Y=await this.getClientSecret(),_=await c(X,Y);if(!_)return;let W=await O.selectFrom("oauth_access_tokens").where("id","=",Number(_)).selectAll().executeTakeFirst();if(!W||W.token!==l(Q))return;if(W.expires_at&&new Date(String(W.expires_at))<new Date){await O.deleteFrom("oauth_access_tokens").where("id","=",W.id).execute();return}if(W.revoked)return;if(this.currentToken=await this.getTokenFromId(W.id)??void 0,await O.updateTable("oauth_access_tokens").set({updated_at:P(new Date)}).where("id","=",W.id).execute(),!W?.user_id)return;return await $0.find(W.user_id)}static async currentAccessToken(){if(this.currentToken)return this.currentToken;let $=this.getBearerToken();if(!$)return;let Z=this.parseToken($);if(!Z)return;let Q=await this.getClientSecret(),X=await c(Z.encryptedId,Q);if(!X)return;let Y=await this.getTokenFromId(Number(X));if(Y)this.currentToken=Y;return Y??void 0}static async tokenCan($){let Z=await this.currentAccessToken();if(!Z)return!1;if(Z.abilities.includes("*"))return!0;return Z.abilities.includes($)}static async tokenCant($){return!await this.tokenCan($)}static async tokenAbilities(){return(await this.currentAccessToken())?.abilities??[]}static async tokenCanAll($){for(let Z of $)if(!await this.tokenCan(Z))return!1;return!0}static async tokenCanAny($){for(let Z of $)if(await this.tokenCan(Z))return!0;return!1}static async tokens($){let Z=$??await this.id();if(!Z)return[];return(await O.selectFrom("oauth_access_tokens").where("user_id","=",Z).where("revoked","=",!1).selectAll().execute()).map((X)=>({id:Number(X.id),userId:Number(X.user_id),clientId:Number(X.oauth_client_id),name:String(X.name||"auth-token"),scopes:y(String(X.scopes??"")),abilities:y(String(X.scopes??"")),expiresAt:X.expires_at?new Date(String(X.expires_at)):null,createdAt:X.created_at?new Date(String(X.created_at)):new Date,updatedAt:X.updated_at?new Date(String(X.updated_at)):new Date,revoked:!!X.revoked}))}static async revokeToken($){let Z=this.parseToken($);if(!Z)return;let Q=await this.getClientSecret(),X=await c(Z.encryptedId,Q);if(!X)return;await O.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:P(new Date)}).where("id","=",Number(X)).execute()}static async revokeTokenById($){await O.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:P(new Date)}).where("id","=",$).execute()}static async revokeAllTokens($){let Z=$??await this.id();if(!Z)return;await O.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:P(new Date)}).where("user_id","=",Z).execute()}static async revokeOtherTokens($){let Z=$??await this.id();if(!Z)return;let Q=await this.currentAccessToken();if(!Q)return;await O.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:P(new Date)}).where("user_id","=",Z).where("id","!=",Q.id).execute()}static async pruneExpiredTokens(){let $=await O.deleteFrom("oauth_access_tokens").where("expires_at","<",P(new Date)).executeTakeFirst();return Number($?.numDeletedRows)||0}static async pruneRevokedTokens(){let $=await O.deleteFrom("oauth_access_tokens").where("revoked","=",!0).executeTakeFirst();return Number($?.numDeletedRows)||0}static async rotateToken($){let Z=this.parseToken($);if(!Z)return null;let{plainToken:Q,encryptedId:X}=Z,Y=await this.getClientSecret(),_=await c(X,Y);if(!_)return null;let W=await O.selectFrom("oauth_access_tokens").where("id","=",Number(_)).selectAll().executeTakeFirst();if(!W)return null;let J=l(Q),G=String(W.token);if(J.length!==G.length)return null;if(!z0(A.from(J,"utf-8"),A.from(G,"utf-8")))return null;let B=W.expires_at?new Date(String(W.expires_at)).getTime():Date.now()+(U.auth.tokenExpiry??3600000),V=Math.max(1,Math.floor((B-Date.now())/1000)),q=_0.generateJWT(W.user_id,V);await O.updateTable("oauth_access_tokens").set({token:l(q),updated_at:P(new Date)}).where("id","=",W.id).execute();let j=await N0(W.id.toString(),Y);return`${q}:${j}`}static async findToken($){return this.getTokenFromId($)}static async once($){let Z=U.auth.username||"email",Q=U.auth.password||"password",X=$[Z];if(!X)return!1;let Y=await $0.where("email","=",X).first(),_=$[Q]||"",W=Y?.password||"$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";if(await H0(_,W)&&Y)return this.authUser=Y,!0;return!1}static guard($){return this}static viaRemember(){return!1}static clearState(){this.authUser=void 0,this.currentToken=void 0,this.clientSecret=void 0}}import{generateTOTP as l4,generateTOTPSecret as d4,totpKeyUri as n4,verifyTOTP as i4}from"@stacksjs/ts-auth";function t4(){return d4()}async function c6($){return l4({secret:$})}async function l6($,Z){return i4($,{secret:Z})}function d6($,Z,Q){let X=$||"johndoe@example.com",Y=Z||"StacksJS 2fa",_=Q||t4();return n4(X,Y,_)}import{randomBytes as o4}from"crypto";import{db as U0}from"@stacksjs/database";import{formatDate as a4}from"@stacksjs/orm";import{HttpError as r4,ok as s4}from"@stacksjs/error-handling";async function r6(){let $=o4(40).toString("hex");if(await U0.insertInto("oauth_clients").values({name:"Personal Access Client",secret:$,provider:"local",redirect:"http://localhost",personal_access_client:!0,password_client:!1,revoked:!1,created_at:a4(new Date)}).execute(),!(await U0.selectFrom("oauth_clients").where("secret","=",$).select(["id"]).executeTakeFirst())?.id)throw new r4(500,"Failed to create personal access client");return s4($)}async function e4($){let Z=$.bearerToken?.();if(!Z){let Y=$.headers?.get?.("authorization")||$.headers?.get?.("Authorization");if(Y&&Y.startsWith("Bearer "))Z=Y.substring(7)}if(!Z){let Y=Error("No authentication token provided.");throw Y.statusCode=401,Y}let Q=await R.getUserFromToken(Z);if(!Q){let Y=Error("Invalid or expired authentication token.");throw Y.statusCode=401,Y}R.setUser(Q),$._authenticatedUser=Q;let X=await R.currentAccessToken();$._currentAccessToken=X}var $7={name:"auth",handle:e4};import{db as v0}from"@stacksjs/database";import{generateRegistrationOptions as G7,generateAuthenticationOptions as L7,verifyRegistrationResponse as F7,verifyAuthenticationResponse as K7,startRegistration as V7,startAuthentication as B7,browserSupportsWebAuthn as O7,browserSupportsWebAuthnAutofill as D7,platformAuthenticatorIsAvailable as q7}from"@stacksjs/ts-auth";async function X7($){return await v0.selectFrom("passkeys").selectAll().where("user_id","=",$).execute()}async function Y7($,Z){return await v0.selectFrom("passkeys").selectAll().where("id","=",Z).where("user_id","=",$).executeTakeFirst()}async function W7($,Z){let Q=Z.registrationInfo?.credential.id,X=Z.registrationInfo?.credential.publicKey;if(!Q)throw Error("[auth/passkey] WebAuthn registration response is missing credential.id");if(!X)throw Error("[auth/passkey] WebAuthn registration response is missing credential.publicKey");let Y={id:Q,cred_public_key:JSON.stringify(X),user_id:$.id,webauthn_user_id:$.email||"",counter:Z.registrationInfo?.credential.counter||0,credential_type:Z.registrationInfo?.credentialType||"",device_type:Z.registrationInfo?.credentialDeviceType||"",backup_eligible:!1,backup_status:Z.registrationInfo?.credentialBackedUp||!1,transports:JSON.stringify(["internal"]),last_used_at:$5()};await v0.insertInto("passkeys").values(Y).executeTakeFirstOrThrow()}function $5(){let $=new Date,Z=(G)=>String(G).padStart(2,"0"),Q=$.getFullYear(),X=Z($.getMonth()+1),Y=Z($.getDate()),_=Z($.getHours()),W=Z($.getMinutes()),J=Z($.getSeconds());return`${Q}-${X}-${Y} ${_}:${W}:${J}`}import{randomBytes as Z5}from"crypto";import{config as h}from"@stacksjs/config";import{db as G0}from"@stacksjs/database";import{mail as f0,template as b0}from"@stacksjs/email";import{makeHash as I0,verifyHash as T0}from"@stacksjs/security";function E0(){return h.auth.passwordReset?.expire??60}async function Q5($){let Z=h.app.name||"Stacks",Q=h.app.supportEmail||h.email?.from?.address||"",X=new Date().toLocaleString("en-US",{dateStyle:"full",timeStyle:"short"});try{let{html:Y,text:_}=await b0("password-changed",{subject:`Your ${Z} password has been changed`,variables:{changedAt:X,supportEmail:Q}});await f0.send({to:$,subject:`Your ${Z} password has been changed`,text:_,html:Y})}catch(Y){console.error("[PasswordReset] Failed to send password changed notification:",Y)}}function U7($){function Z(){return Z5(32).toString("hex")}async function Q(){let W=Z(),J=await I0(W,{algorithm:"bcrypt"});return await G0.insertInto("password_resets").values({email:$,token:J}).executeTakeFirst(),W}async function X(){let W=await Q(),G=`${h.app.url?`https://${h.app.url}`:`http://localhost:${process.env.PORT||"3000"}`}/password/reset/${W}?email=${encodeURIComponent($)}`,K=E0(),B=h.app.name||"Stacks",{html:V,text:q}=await b0("password-reset",{subject:`Reset Your ${B} Password`,variables:{resetUrl:G,expireMinutes:K}});await f0.send({to:$,subject:`Reset Your ${B} Password`,text:q,html:V})}async function Y(W){let J=await G0.selectFrom("password_resets").where("email","=",$).selectAll().executeTakeFirst();if(!J)return!1;let G=E0(),K=new Date(J.created_at);if((new Date().getTime()-K.getTime())/60000>G)return await G0.deleteFrom("password_resets").where("email","=",$).execute(),!1;let q=J.token;return await T0(W,q)}async function _(W,J){let G=await G0.transaction(async(K)=>{let B=K,V=await B.selectFrom("password_resets").where("email","=",$).selectAll().executeTakeFirst();if(!V)return{success:!1,message:"Invalid or expired reset token"};let q=E0(),j=new Date(V.created_at);if((new Date().getTime()-j.getTime())/60000>q)return await B.deleteFrom("password_resets").where("email","=",$).execute(),{success:!1,message:"This password reset link has expired. Please request a new one."};let T=V.token;if(!await T0(W,T))return{success:!1,message:"Invalid or expired reset token"};if(!await B.selectFrom("users").where("email","=",$).selectAll().executeTakeFirst())return{success:!1,message:"Invalid or expired reset token"};let q0=await I0(J,{algorithm:"bcrypt"});return await B.updateTable("users").set({password:q0}).where("email","=",$).executeTakeFirst(),await B.deleteFrom("password_resets").where("email","=",$).execute(),{success:!0}});if(G.success)Q5($).catch((K)=>{console.error("[PasswordReset] Failed to send notification:",K)});return G}return{sendEmail:X,verifyToken:Y,resetPassword:_}}import{db as X5}from"@stacksjs/database";import{HttpError as S0}from"@stacksjs/error-handling";import{User as g0}from"@stacksjs/orm";import{makeHash as Y5}from"@stacksjs/security";var W5=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;async function R7($){let{email:Z,password:Q,name:X}=$;if(typeof Z!=="string"||Z.length>254||!W5.test(Z))throw new S0(422,"Email address is invalid");if(typeof Q!=="string"||Q.length<8)throw new S0(422,"Password must be at least 8 characters");if(await g0.where("email","=",Z).first())throw new S0(409,"Email already exists");let _=await Y5(Q,{algorithm:"bcrypt"});await X5.insertInto("users").values({email:Z,password:_,name:X}).execute();let W=await g0.where("email","=",Z).first();if(!W)throw Error("Failed to retrieve created user");return{token:await R.createToken(W,"user-auth-token")}}import{request as d}from"@stacksjs/router";async function Z0(){let $=d?._authenticatedUser;if($)return $;let Z=d.bearerToken?.();if(!Z){let Q=d.headers?.get?.("authorization")||d.headers?.get?.("Authorization");if(Q&&Q.startsWith("Bearer "))Z=Q.substring(7)}if(!Z)return;return await R.getUserFromToken(Z)}async function I7(){return Z0()}async function _5(){return!!await Z0()}async function T7(){return(await Z0())?.id}async function f7(){return(await Z0())?.email}async function b7(){return(await Z0())?.name}async function g7(){return await _5()}async function k7(){await R.logout()}async function h7(){if(d?._authenticatedUser)d._authenticatedUser=void 0}t();t();t();import{log as f}from"@stacksjs/logging";import*as B0 from"@stacksjs/path";class G5{allow($){return z.allow($)}deny($,Z){return z.deny($,Z)}denyIf($,Z){if($)return this.deny(Z);return!0}denyUnless($,Z){if(!$)return this.deny(Z);return!0}allowIf($,Z){if($)return this.allow(Z);return!1}}async function L5(){let{fs:$}=await import("@stacksjs/storage"),Z=B0.appPath("Policies");if(!$.existsSync(Z)){f.debug("No Policies directory found");return}try{let _=(await import(B0.appPath("Gates.ts"))).policies||{};for(let[W,J]of Object.entries(_)){let G=typeof J==="string"?J:J.policy,K=`${Z}/${G}.ts`;if($.existsSync(K)){let B=await import(K),V=B.default||B[G];if(V)X0(W,V),f.debug(`Registered policy: ${G} for ${W}`)}}}catch{f.debug("No Gates.ts found, using convention-based discovery")}let Q=$.readdirSync(Z).filter((X)=>X.endsWith("Policy.ts"));for(let X of Q){let Y=X.replace(".ts",""),_=Y.replace("Policy",""),W=`${Z}/${X}`;try{let J=await import(W),G=J.default||J[Y];if(G)X0(_,G),f.debug(`Auto-discovered policy: ${Y} for ${_}`)}catch(J){f.error(`Failed to load policy ${Y}:`,J)}}}async function F5(){let{define:$,before:Z,after:Q}=await Promise.resolve().then(() => (t(),r0));try{let Y=await import(B0.appPath("Gates.ts")),_=Y.gates||Y.default?.gates||{};for(let[G,K]of Object.entries(_))if(typeof K==="function")$(G,K),f.debug(`Registered gate: ${G}`);let W=Y.before||Y.default?.before||[];for(let G of W)if(typeof G==="function")Z(G);let J=Y.after||Y.default?.after||[];for(let G of J)if(typeof G==="function")Q(G);f.debug("Gates registered successfully")}catch{f.debug("No Gates.ts found or failed to load")}}async function l7(){await F5(),await L5()}t();async function K5($,Z,...Q){return L0(Z,$,...Q)}async function V5($,Z,...Q){return F0(Z,$,...Q)}async function B5($,Z,...Q){return Y0(Z,$,...Q)}async function O5($,Z,...Q){return K0(Z,$,...Q)}async function D5($,Z,...Q){return V0(Z,$,...Q)}async function i7($,Z,...Q){return n(Z,$,...Q)}function t7($){return Object.assign($,{can:(Q,...X)=>K5($,Q,...X),cannot:(Q,...X)=>V5($,Q,...X),canAny:(Q,...X)=>B5($,Q,...X),canAll:(Q,...X)=>O5($,Q,...X),authorize:(Q,...X)=>D5($,Q,...X)})}var L={userRoles:new Map,userPermissions:new Map,rolePermissions:new Map,roles:new Map,permissions:new Map},x0=null;function q5($){x0=$,O0()}function D(){if(!x0)throw Error("RBAC store not configured. Call setRbacStore() first.");return x0}function O0(){L.userRoles.clear(),L.userPermissions.clear(),L.rolePermissions.clear(),L.roles.clear(),L.permissions.clear()}function S($){if(typeof $==="number")return $;return $.id}async function C5($,Z="web",Q){let X=await D().createRole($,Z,Q);return L.roles.set(`${$}:${Z}`,X),X}async function b($,Z="web"){let Q=`${$}:${Z}`;if(L.roles.has(Q))return L.roles.get(Q);let X=await D().findRoleByName($,Z);if(X)L.roles.set(Q,X);return X}async function j5($,Z="web"){let Q=await b($,Z);if(Q)await D().deleteRole(Q.id),L.roles.delete(`${$}:${Z}`),O0()}async function M5($){return D().getAllRoles($)}async function z5($,Z="web",Q){let X=await D().createPermission($,Z,Q);return L.permissions.set(`${$}:${Z}`,X),X}async function g($,Z="web"){let Q=`${$}:${Z}`;if(L.permissions.has(Q))return L.permissions.get(Q);let X=await D().findPermissionByName($,Z);if(X)L.permissions.set(Q,X);return X}async function H5($,Z="web"){let Q=await g($,Z);if(Q)await D().deletePermission(Q.id),L.permissions.delete(`${$}:${Z}`),O0()}async function U5($){return D().getAllPermissions($)}async function o($){let Z=S($);if(L.userRoles.has(Z))return L.userRoles.get(Z);let Q=await D().getUserRoles(Z);return L.userRoles.set(Z,Q),Q}async function s0($,Z,Q="web"){let X=S($),Y=await b(Z,Q);if(!Y)throw Error(`Role '${Z}' not found.`);await D().assignRoleToUser(X,Y.id),L.userRoles.delete(X),L.userPermissions.delete(X)}async function e0($,Z,Q="web"){let X=S($),Y=await b(Z,Q);if(!Y)return;await D().removeRoleFromUser(X,Y.id),L.userRoles.delete(X),L.userPermissions.delete(X)}async function v5($){let Z=S($);await D().removeAllRolesFromUser(Z),L.userRoles.delete(Z),L.userPermissions.delete(Z)}async function $4($,Z,Q="web"){let X=S($),Y=[];for(let _ of Z){let W=await b(_,Q);if(!W)throw Error(`Role '${_}' not found.`);Y.push(W.id)}await D().syncUserRoles(X,Y),L.userRoles.delete(X),L.userPermissions.delete(X)}async function Z4($,Z,Q="web"){return(await o($)).some((Y)=>Y.name===Z&&Y.guard_name===Q)}async function Q4($,Z,Q="web"){let X=await o($);return Z.some((Y)=>X.some((_)=>_.name===Y&&_.guard_name===Q))}async function X4($,Z,Q="web"){let X=await o($);return Z.every((Y)=>X.some((_)=>_.name===Y&&_.guard_name===Q))}async function W0($){let Z=S($);if(L.userPermissions.has(Z))return L.userPermissions.get(Z);let Q=await D().getUserDirectPermissions(Z),X=await o($),Y=[];for(let J of X){let G=await F4(J.id);Y.push(...G)}let _=new Set,W=[];for(let J of[...Q,...Y])if(!_.has(J.id))_.add(J.id),W.push(J);return L.userPermissions.set(Z,W),W}async function Y4($,Z,Q="web"){let X=S($),Y=await g(Z,Q);if(!Y)throw Error(`Permission '${Z}' not found.`);await D().assignPermissionToUser(X,Y.id),L.userPermissions.delete(X)}async function W4($,Z,Q="web"){let X=S($),Y=await g(Z,Q);if(!Y)return;await D().removePermissionFromUser(X,Y.id),L.userPermissions.delete(X)}async function E5($){let Z=S($);await D().removeAllPermissionsFromUser(Z),L.userPermissions.delete(Z)}async function _4($,Z,Q="web"){let X=S($),Y=[];for(let _ of Z){let W=await g(_,Q);if(!W)throw Error(`Permission '${_}' not found.`);Y.push(W.id)}await D().syncUserPermissions(X,Y),L.userPermissions.delete(X)}async function J4($,Z,Q="web"){return(await W0($)).some((Y)=>Y.name===Z&&Y.guard_name===Q)}async function G4($,Z,Q="web"){let X=await W0($);return Z.some((Y)=>X.some((_)=>_.name===Y&&_.guard_name===Q))}async function L4($,Z,Q="web"){let X=await W0($);return Z.every((Y)=>X.some((_)=>_.name===Y&&_.guard_name===Q))}async function F4($){if(L.rolePermissions.has($))return L.rolePermissions.get($);let Z=await D().getRolePermissions($);return L.rolePermissions.set($,Z),Z}async function S5($,Z,Q="web"){let X=await b($,Q);if(!X)throw Error(`Role '${$}' not found.`);let Y=await g(Z,Q);if(!Y)throw Error(`Permission '${Z}' not found.`);await D().assignPermissionToRole(X.id,Y.id),L.rolePermissions.delete(X.id),L.userPermissions.clear()}async function x5($,Z,Q="web"){let X=await b($,Q);if(!X)return;let Y=await g(Z,Q);if(!Y)return;await D().removePermissionFromRole(X.id,Y.id),L.rolePermissions.delete(X.id),L.userPermissions.clear()}async function P5($,Z,Q="web"){let X=await b($,Q);if(!X)throw Error(`Role '${$}' not found.`);let Y=[];for(let _ of Z){let W=await g(_,Q);if(!W)throw Error(`Permission '${_}' not found.`);Y.push(W.id)}await D().syncRolePermissions(X.id,Y),L.rolePermissions.delete(X.id),L.userPermissions.clear()}function A5($){let Z=S($);return Object.assign($,{hasRole:(X,Y)=>Z4(Z,X,Y),hasAnyRole:(X,Y)=>Q4(Z,X,Y),hasAllRoles:(X,Y)=>X4(Z,X,Y),hasPermission:(X,Y)=>J4(Z,X,Y),hasAnyPermission:(X,Y)=>G4(Z,X,Y),hasAllPermissions:(X,Y)=>L4(Z,X,Y),getRoles:()=>o(Z),getPermissions:()=>W0(Z),assignRole:(X,Y)=>s0(Z,X,Y),removeRole:(X,Y)=>e0(Z,X,Y),syncRoles:(X,Y)=>$4(Z,X,Y),givePermission:(X,Y)=>Y4(Z,X,Y),revokePermission:(X,Y)=>W4(Z,X,Y),syncPermissions:(X,Y)=>_4(Z,X,Y)})}var a7={setStore:q5,flushCache:O0,createRole:C5,findRole:b,deleteRole:j5,getAllRoles:M5,createPermission:z5,findPermission:g,deletePermission:H5,getAllPermissions:U5,getUserRoles:o,assignRole:s0,removeRole:e0,removeAllRoles:v5,syncRoles:$4,hasRole:Z4,hasAnyRole:Q4,hasAllRoles:X4,getUserPermissions:W0,givePermission:Y4,revokePermission:W4,revokeAllPermissions:E5,syncPermissions:_4,hasPermission:J4,hasAnyPermission:G4,hasAllPermissions:L4,getRolePermissions:F4,givePermissionToRole:S5,revokePermissionFromRole:x5,syncRolePermissions:P5,withRbac:A5};import{Buffer as K4}from"buffer";import{createHmac as B4,randomBytes as R5,timingSafeEqual as w5}from"crypto";import{config as a}from"@stacksjs/config";import{db as p}from"@stacksjs/database";import{mail as V4,template as N5}from"@stacksjs/email";import{log as y5}from"@stacksjs/logging";function I5($){let Z=R5(32).toString("hex"),Q=a.app.key||"stacks-default-key",X=`${$}:${Z}`,Y=B4("sha256",Q).update(X).digest("hex");return{token:Z,hash:Y}}function T5($,Z,Q){let X=a.app.key||"stacks-default-key",Y=`${$}:${Z}`,_=B4("sha256",X).update(Y).digest("hex"),W=K4.from(_),J=K4.from(Q);if(W.length!==J.length)return!1;return w5(W,J)}function f5(){let Z=(a.auth??{}).emailVerification;if(Z!=null&&typeof Z==="object"){let Q=Z;if(typeof Q.expire==="number")return Q.expire}return 60}function O4($){return $.email_verified_at!=null}async function D4($){let{token:Z,hash:Q}=I5($.id),X=f5(),Y=new Date(Date.now()+X*60*1000);await p.deleteFrom("email_verifications").where("user_id","=",$.id).execute(),await p.insertInto("email_verifications").values({user_id:$.id,token:Q,expires_at:Y.toISOString()}).executeTakeFirst();let W=`${a.app.url?`https://${a.app.url}`:`http://localhost:${process.env.PORT||"3000"}`}/verify-email/${$.id}/${Z}`,J=a.app.name||"Stacks";try{let{html:G,text:K}=await N5("email-verification",{subject:`Verify Your ${J} Email Address`,variables:{verificationUrl:W,expiryMinutes:X,userName:$.name||$.email}});await V4.send({to:$.email,subject:`Verify Your ${J} Email Address`,text:K,html:G})}catch(G){let K=G instanceof Error?G.message:String(G);y5.warn(`[email] Email verification template failed, using plain text fallback: ${K}`),await V4.send({to:$.email,subject:`Verify Your ${J} Email Address`,text:`Please verify your email address by visiting: ${W}
159
+ SET revoked = ${v}, updated_at = ${U}
160
+ WHERE id = ${z(1)}
161
+ `,[$])}function f($){if(!$)return[];if(Array.isArray($))return $;try{let W=JSON.parse($);return Array.isArray(W)?W:[]}catch{return[]}}function w0($){return{id:$.id,name:$.name,secret:$.secret,provider:$.provider,redirect:$.redirect,personalAccessClient:Boolean($.personal_access_client),passwordClient:Boolean($.password_client),revoked:Boolean($.revoked),createdAt:$.created_at?new Date($.created_at):new Date,updatedAt:$.updated_at?new Date($.updated_at):null}}var H$,J1,r,qZ,U,v,K0,U$;var L0=$1(()=>{H$=D$.DB_CONNECTION||"sqlite",J1=j$(H$),{isPostgres:r,isMysql:qZ,now:U,boolTrue:v,boolFalse:K0}=J1;U$=n});var w1={};e0(w1,{policy:()=>O0,none:()=>x1,inspect:()=>W0,hasPolicy:()=>N1,has:()=>A1,getPolicyFor:()=>R1,flush:()=>T1,denies:()=>E1,define:()=>C1,default:()=>MW,cannot:()=>S0,can:()=>v0,before:()=>P1,authorize:()=>x0,any:()=>_0,allows:()=>S1,all:()=>E0,after:()=>v1,abilities:()=>y1,Gate:()=>f1,AuthorizationResponse:()=>q,AuthorizationException:()=>B0});class q{isAllowed;message;code;constructor($,W,Z){this.isAllowed=$,this.message=W,this.code=Z}static allow($){return new q(!0,$)}static deny($,W){return new q(!1,$||"This action is unauthorized.",W)}allowed(){return this.isAllowed}denied(){return!this.isAllowed}authorize(){if(!this.isAllowed)throw new B0(this.message||"This action is unauthorized.",this.code)}}function C1($,W){C.gates.set($,W)}function O0($,W){let Z=typeof $==="string"?$:$.name;C.policies.set(Z,W)}function P1($){C.beforeCallbacks.push($)}function v1($){C.afterCallbacks.push($)}async function S1($,W,...Z){return Z0($,W,...Z)}async function E1($,W,...Z){return!await Z0($,W,...Z)}async function v0($,W,...Z){return Z0($,W,...Z)}async function S0($,W,...Z){return!await Z0($,W,...Z)}async function _0($,W,...Z){for(let J of $)if(await Z0(J,W,...Z))return!0;return!1}async function E0($,W,...Z){for(let J of $)if(!await Z0(J,W,...Z))return!1;return!0}async function x1($,W,...Z){return!await _0($,W,...Z)}async function x0($,W,...Z){let J=await W0($,W,...Z);if(!J.isAllowed)throw new B0(J.message,J.code);return J}async function W0($,W,...Z){for(let G of C.beforeCallbacks){let X=await G(W,$,Z);if(X===!0)return q.allow();if(X===!1)return q.deny()}let J=Z[0];if(J&&typeof J==="object"){let G=J.constructor?.name,X=C.policies.get(G);if(X){let K=new X;if(K.before){let L=await K.before(W,$);if(L===!0)return q.allow();if(L===!1)return q.deny()}let Y=K[$];if(Y){let L=await Y.call(K,W,...Z);return q1(L??!1)}}}let Q=C.gates.get($);if(Q){let G=await Q(W,...Z),X=q1(G);for(let K of C.afterCallbacks){let Y=await K(W,$,X.isAllowed,Z);if(typeof Y==="boolean")return Y?q.allow():q.deny()}return X}return q.deny(`No gate or policy defined for ability: ${$}`)}async function Z0($,W,...Z){return(await W0($,W,...Z)).isAllowed}function q1($){if($ instanceof q)return $;if(typeof $!=="boolean")throw TypeError(`[gate] Policy must return boolean or AuthorizationResponse; got ${typeof $}. If you returned a model/value by mistake, return \`true\`/\`false\` instead.`);return $?q.allow():q.deny()}function R1($){if(!$||typeof $!=="object")return null;let W=$.constructor?.name,Z=C.policies.get(W);if(Z)return new Z;return null}function A1($){return C.gates.has($)}function N1($){let W=typeof $==="string"?$:$.name;return C.policies.has(W)}function y1(){return Array.from(C.gates.keys())}function T1(){C.gates.clear(),C.policies.clear(),C.beforeCallbacks=[],C.afterCallbacks=[]}var B0,C,f1,MW;var J0=$1(()=>{B0=class B0 extends Error{code;status;constructor($="This action is unauthorized.",W,Z=403){super($);this.code=W;this.status=Z;this.name="AuthorizationException"}};C={gates:new Map,policies:new Map,beforeCallbacks:[],afterCallbacks:[]};f1={define:C1,policy:O0,before:P1,after:v1,allows:S1,denies:E1,can:v0,cannot:S0,any:_0,all:E0,none:x1,authorize:x0,inspect:W0,has:A1,hasPolicy:N1,abilities:y1,getPolicyFor:R1,flush:T1,AuthorizationResponse:q,AuthorizationException:B0},MW=f1});import{config as S}from"@stacksjs/config";import{db as M}from"@stacksjs/database";import{HttpError as I0}from"@stacksjs/error-handling";import{formatDate as i,User as e}from"@stacksjs/orm";import{getCurrentRequest as h$,request as b0}from"@stacksjs/router";import{Buffer as H0}from"buffer";import{createHash as p$,timingSafeEqual as k0}from"crypto";import{decrypt as L1,encrypt as m$,verifyHash as M0}from"@stacksjs/security";import{log as V1}from"@stacksjs/logging";var a="$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";import{HttpError as L$}from"@stacksjs/error-handling";var V$=5,W1=900000,F$=1e4,B$=300000;class T0{store=new Map;lastEviction=Date.now();evict(){let $=Date.now(),W=$-this.lastEviction>=B$,Z=this.store.size>=F$;if(!W&&!Z)return;this.lastEviction=$;for(let[J,Q]of this.store)if(Q.lockedUntil>0&&Q.lockedUntil<=$)this.store.delete(J);else if(Q.lockedUntil===0&&Q.attempts===0)this.store.delete(J)}get($){return this.evict(),this.store.get($)}set($,W){this.store.set($,W)}delete($){this.store.delete($)}}class Z1{prefix="auth:ratelimit:";async get($){let{cache:W}=await import("@stacksjs/cache"),Z=await W.get(`${this.prefix}${$}`);if(Z==null)return;try{return typeof Z==="string"?JSON.parse(Z):Z}catch{return}}async set($,W,Z){let{cache:J}=await import("@stacksjs/cache");await J.set(`${this.prefix}${$}`,JSON.stringify(W),Math.ceil(Z/1000))}async delete($){let{cache:W}=await import("@stacksjs/cache");await W.remove(`${this.prefix}${$}`)}}var g=new T0;class k{static useStore($){g=$}static useSharedStore(){g=new Z1}static useMemoryStore(){g=new T0}static async isRateLimited($){$=$.toLowerCase();let W=Date.now(),Z=await g.get($);if(!Z)return!1;if(Z.lockedUntil>0&&Z.lockedUntil<=W)return await g.delete($),!1;return Z.lockedUntil>0}static async recordFailedAttempt($){$=$.toLowerCase();let W=Date.now(),Z=await g.get($)||{attempts:0,lockedUntil:0};if(Z.attempts++,Z.attempts>=V$)Z.lockedUntil=W+W1,Z.attempts=0;await g.set($,Z,W1)}static async resetAttempts($){await g.delete($.toLowerCase())}static async validateAttempt($){if(await this.isRateLimited($))throw new L$(429,"Too many login attempts. Please try again later.")}}L0();var F1=Symbol.for("stacks.requestAuthState");function x(){let $=h$();if(!$)return null;let W=$[F1];if(!W)W={},$[F1]=W;return W}function V0($){return p$("sha256").update($).digest("hex")}class R{static getBearerToken(){let $=b0.bearerToken?.();if(!$){let W=b0.headers?.get?.("authorization")||b0.headers?.get?.("Authorization");if(W&&W.startsWith("Bearer "))$=W.substring(7)}return $||null}static parseToken($){let W=$.indexOf(":");if(W===-1)return null;let Z=$.substring(0,W),J=$.substring(W+1);if(!Z||!J)return null;return{plainToken:Z,encryptedId:J}}static async getClientSecret(){let $=x();if($?.clientSecret)return $.clientSecret;let W=await this.getPersonalAccessClient();if($)$.clientSecret=W.secret;return W.secret}static async encryptTokenId($){return await m$(String($))}static async decryptTokenId($){try{return await L1($)}catch{try{let W=await this.getClientSecret();return await L1($,W)}catch{return null}}}static async getPersonalAccessClient(){try{let $=await M.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).selectAll().executeTakeFirst();if(!$)throw new I0(500,"No personal access client found. Please run `./buddy auth:setup` first.");return $}catch($){if($ instanceof Error&&$.message.includes("does not exist"))throw new I0(500,"OAuth tables not found. Please run `./buddy auth:setup` first.");throw $}}static async validateClient($,W){let Z=await M.selectFrom("oauth_clients").where("id","=",$).where("revoked","=",!1).selectAll().executeTakeFirst(),J=H0.from(W);if(!Z?.secret){let X=H0.alloc(Math.max(J.length,1)),K=J.length>0?J:H0.alloc(1);return k0(X,K),!1}let Q=String(Z.secret);if(Q.startsWith("$2"))return await M0(W,Q);let G=H0.from(Q);if(G.length!==J.length)return k0(G,G),!1;return k0(G,J)}static async getTokenFromId($){let W=await M.selectFrom("oauth_access_tokens").where("id","=",$).selectAll().executeTakeFirst();if(!W)return null;let Z=W;return{id:Z.id,userId:Z.user_id,clientId:Z.oauth_client_id,name:Z.name||"auth-token",scopes:f(Z.scopes),abilities:f(Z.scopes),expiresAt:Z.expires_at?new Date(String(Z.expires_at)):null,createdAt:Z.created_at?new Date(String(Z.created_at)):new Date,updatedAt:Z.updated_at?new Date(String(Z.updated_at)):new Date,revoked:!!Z.revoked}}static async attempt($){let W=S.auth.username||"email",Z=S.auth.password||"password",J=$[W];if(!J)return!1;let Q=await k.isRateLimited(J),G=await e.where("email","=",J).first(),X=$[Z]||"",K=G?.password||a,Y=await M0(X,K);if(Q)return!1;if(Y&&G){await k.resetAttempts(J);let L=x();if(L)L.authUser=G;return!0}return await k.recordFailedAttempt(J),!1}static async validate($){let W=S.auth.username||"email",Z=S.auth.password||"password",J=$[W];if(!J)return!1;let Q=await e.where("email","=",J).first(),G=$[Z]||"",X=Q?.password||a;return await M0(G,X)&&!!Q}static async login($,W){let Z=await this.attempt($),J=x()?.authUser;if(!Z||!J)return null;let{plainTextToken:Q,refreshToken:G,expiresIn:X}=await this.createTokenForUser(J,W);return{user:J,token:Q,refreshToken:G,expiresIn:X}}static async loginUsingId($,W){let Z=await e.find($);if(!Z)return null;let J=x();if(J)J.authUser=Z;let{plainTextToken:Q,refreshToken:G,expiresIn:X}=await this.createTokenForUser(Z,W);return{user:Z,token:Q,refreshToken:G,expiresIn:X}}static async logout(){let $=this.getBearerToken();if($){let Z=await M.selectFrom("oauth_access_tokens").where("token","=",V0($)).select(["id"]).executeTakeFirst();if(Z)await M.updateTable("oauth_refresh_tokens").set({revoked:!0}).where("access_token_id","=",Number(Z.id)).execute();await this.revokeToken($)}let W=x();if(W)W.authUser=void 0,W.currentToken=void 0}static async user(){let $=x();if($?.authUser)return $.authUser;let W=this.getBearerToken();if(!W)return;let Z=await this.getUserFromToken(W);if(Z&&$)$.authUser=Z;return Z}static async check(){return await this.user()!==void 0}static async guest(){return!await this.check()}static async id(){return(await this.user())?.id}static setUser($){let W=x();if(W)W.authUser=$}static async createTokenForUser($,W){let Z=W?.name??S.auth.defaultTokenName??"auth-token",J=W?.abilities??W?.scopes??S.auth.defaultAbilities??["*"],Q=W?.expiresInMinutes!==void 0?W.expiresInMinutes*60*1000:S.auth.tokenExpiry??3600000,X=W?.expiresAt??new Date(Date.now()+Q),K=Math.max(1,Math.floor((X.getTime()-Date.now())/60000)),Y=W?.refreshExpiresInDays??Math.max(1,Math.round((S.auth.refreshTokenExpiry??2592000000)/86400000));V1.debug(`[auth] Creating token for user#${$.id}: ${Z}`);let L=await f0($.id,Z,J,{expiresInMinutes:K,withRefreshToken:W?.withRefreshToken!==!1,refreshExpiresInDays:Y}),O=L.plainTextToken;return{accessToken:{id:L.accessToken.id,userId:L.accessToken.userId,clientId:L.accessToken.clientId,name:L.accessToken.name,scopes:L.accessToken.scopes,abilities:J,expiresAt:L.accessToken.expiresAt??X,createdAt:L.accessToken.createdAt,updatedAt:L.accessToken.updatedAt,revoked:L.accessToken.revoked,plainTextToken:O},plainTextToken:O,refreshToken:L.refreshToken,expiresIn:L.expiresIn}}static async createToken($,W=S.auth.defaultTokenName||"auth-token",Z=S.auth.defaultAbilities||["*"]){let{plainTextToken:J}=await this.createTokenForUser($,{name:W,abilities:Z});return J}static async requestToken($,W,Z){if(!await this.validateClient(W,Z))throw new I0(401,"Invalid client credentials");let Q=await this.attempt($),G=x()?.authUser;if(!Q||!G)return null;return{token:await this.createToken(G,"user-auth-token")}}static async validateToken($){let W=V0($),Z=await M.selectFrom("oauth_access_tokens").where("token","=",W).selectAll().executeTakeFirst();if(!Z)return!1;if(V1.debug(`[auth] Token validated for token#${Z.id}`),Z.expires_at&&new Date(String(Z.expires_at))<new Date)return await M.deleteFrom("oauth_access_tokens").where("id","=",Z.id).execute(),!1;if(Z.revoked)return!1;if(d(Z.created_at,await s(Z.user_id)))return!1;return await M.updateTable("oauth_access_tokens").set({updated_at:i(new Date)}).where("id","=",Z.id).execute(),!0}static async getUserFromToken($){let W=V0($),Z=await M.selectFrom("oauth_access_tokens").where("token","=",W).selectAll().executeTakeFirst();if(!Z)return;if(Z.expires_at&&new Date(String(Z.expires_at))<new Date){await M.deleteFrom("oauth_access_tokens").where("id","=",Z.id).execute();return}if(Z.revoked)return;let J=x();if(J)J.currentToken=await this.getTokenFromId(Z.id)??void 0;if(await M.updateTable("oauth_access_tokens").set({updated_at:i(new Date)}).where("id","=",Z.id).execute(),!Z?.user_id)return;let Q=await e.find(Z.user_id),G=Q?.password_changed_at,X=G?new Date(String(G)):null;if(d(Z.created_at,X))return;return Q}static async currentAccessToken(){let $=x();if($?.currentToken)return $.currentToken;let W=this.getBearerToken();if(!W)return;let Z=await M.selectFrom("oauth_access_tokens").where("token","=",V0(W)).select(["id"]).executeTakeFirst();if(!Z)return;let J=await this.getTokenFromId(Number(Z.id));if(J&&$)$.currentToken=J;return J??void 0}static async tokenCan($){let W=await this.currentAccessToken();if(!W)return!1;if(W.abilities.includes("*"))return!0;return W.abilities.includes($)}static async tokenCant($){return!await this.tokenCan($)}static async tokenAbilities(){return(await this.currentAccessToken())?.abilities??[]}static async tokenCanAll($){let W=await this.currentAccessToken();if(!W)return!1;if(W.abilities.includes("*"))return!0;return $.every((Z)=>W.abilities.includes(Z))}static async tokenCanAny($){let W=await this.currentAccessToken();if(!W)return!1;if(W.abilities.includes("*"))return!0;return $.some((Z)=>W.abilities.includes(Z))}static async tokens($){let W=$??await this.id();if(!W)return[];return(await M.selectFrom("oauth_access_tokens").where("user_id","=",W).where("revoked","=",!1).selectAll().execute()).map((J)=>({id:Number(J.id),userId:Number(J.user_id),clientId:Number(J.oauth_client_id),name:String(J.name||"auth-token"),scopes:f(String(J.scopes??"")),abilities:f(String(J.scopes??"")),expiresAt:J.expires_at?new Date(String(J.expires_at)):null,createdAt:J.created_at?new Date(String(J.created_at)):new Date,updatedAt:J.updated_at?new Date(String(J.updated_at)):new Date,revoked:!!J.revoked}))}static async revokeToken($){await M.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:i(new Date)}).where("token","=",V0($)).execute()}static async revokeTokenById($){await M.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:i(new Date)}).where("id","=",$).execute()}static async revokeAllTokens($){let W=$??await this.id();if(!W)return;await M.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:i(new Date)}).where("user_id","=",W).execute()}static async revokeOtherTokens($){let W=$??await this.id();if(!W)return;let Z=await this.currentAccessToken();if(!Z)return;await M.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:i(new Date)}).where("user_id","=",W).where("id","!=",Z.id).execute()}static async pruneExpiredTokens(){let $=await M.deleteFrom("oauth_access_tokens").where("expires_at","<",i(new Date)).executeTakeFirst();return Number($?.numDeletedRows)||0}static async pruneRevokedTokens(){let $=await M.deleteFrom("oauth_access_tokens").where("revoked","=",!0).executeTakeFirst();return Number($?.numDeletedRows)||0}static async rotateToken($){let{findToken:W,revokeToken:Z}=await Promise.resolve().then(() => (L0(),K1)),J=await W($);if(!J)return null;let Q=J.expiresAt?J.expiresAt.getTime()-Date.now():S.auth.tokenExpiry??3600000,G=Math.max(1,Math.floor(Q/60000));await Z($);let X=await e.find(J.userId);if(!X)return null;return(await this.createTokenForUser(X,{name:J.name,abilities:J.scopes??["*"],expiresInMinutes:G,withRefreshToken:!1})).plainTextToken}static async findToken($){return this.getTokenFromId($)}static async once($){let W=S.auth.username||"email",Z=S.auth.password||"password",J=$[W];if(!J)return!1;let Q=await e.where("email","=",J).first(),G=$[Z]||"",X=Q?.password||a;if(await M0(G,X)&&Q){let Y=x();if(Y)Y.authUser=Q;return!0}return!1}static guard($){return this}static viaRemember(){return!1}static clearState(){let $=x();if($)$.authUser=void 0,$.currentToken=void 0,$.clientSecret=void 0}}import{generateTOTP as c$,generateTOTPSecret as u$,totpKeyUri as l$,verifyTOTP as d$}from"@stacksjs/ts-auth";function g0(){return u$()}async function kZ($){return c$({secret:$})}async function h0($,W){return d$($,{secret:W})}function B1($,W,Z){let J=$||"johndoe@example.com",Q=W||"StacksJS 2fa",G=Z||g0();return l$(J,Q,G)}import{randomBytes as n$}from"crypto";import{db as U0}from"@stacksjs/database";import{formatDate as i$}from"@stacksjs/orm";import{err as t$,HttpError as o$,ok as a$}from"@stacksjs/error-handling";import{makeHash as r$}from"@stacksjs/security";async function lZ(){if((await U0.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).select(["id"]).executeTakeFirst())?.id)return t$({code:"already-exists",message:"A personal access client already exists. Revoke the existing client (`./buddy auth:revoke-client`) before creating a new one."});let W=n$(40).toString("hex"),Z=await r$(W,{algorithm:"bcrypt"});if(await U0.insertInto("oauth_clients").values({name:"Personal Access Client",secret:Z,provider:"local",redirect:"http://localhost",personal_access_client:!0,password_client:!1,revoked:!1,created_at:i$(new Date)}).execute(),!(await U0.selectFrom("oauth_clients").where("secret","=",Z).select(["id"]).executeTakeFirst())?.id)throw new o$(500,"Failed to create personal access client");return a$(W)}async function s$($){let W=$.bearerToken?.();if(!W){let Q=$.headers?.get?.("authorization")||$.headers?.get?.("Authorization");if(Q&&Q.startsWith("Bearer "))W=Q.substring(7)}if(!W){let Q=Error("No authentication token provided.");throw Q.statusCode=401,Q}let Z=await R.getUserFromToken(W);if(!Z){let Q=Error("Invalid or expired authentication token.");throw Q.statusCode=401,Q}R.setUser(Z),$._authenticatedUser=Z;let J=await R.currentAccessToken();$._currentAccessToken=J}var iZ={name:"auth",handle:s$};import{db as p}from"@stacksjs/database";import{generateRegistrationOptions as J2,generateAuthenticationOptions as Q2,verifyRegistrationResponse as G2,verifyAuthenticationResponse as X2,startRegistration as Y2,startAuthentication as K2,browserSupportsWebAuthn as L2,browserSupportsWebAuthnAutofill as V2,platformAuthenticatorIsAvailable as F2}from"@stacksjs/ts-auth";async function aZ($){return await p.selectFrom("passkeys").selectAll().where("user_id","=",$).execute()}async function e$($,W){return await p.selectFrom("passkeys").selectAll().where("id","=",W).where("user_id","=",$).executeTakeFirst()}async function rZ($,W,Z){let J=await e$($,W);if(!J)return!1;let Q=Number(J.counter??0);if(Z!==0&&Z<=Q)return!1;return await p.updateTable("passkeys").set({counter:Z,last_used_at:O1()}).where("id","=",W).where("user_id","=",$).execute(),!0}async function sZ($,W){let Z=W.registrationInfo?.credential.id,J=W.registrationInfo?.credential.publicKey;if(!Z)throw Error("[auth/passkey] WebAuthn registration response is missing credential.id");if(!J)throw Error("[auth/passkey] WebAuthn registration response is missing credential.publicKey");let Q={id:Z,cred_public_key:JSON.stringify(J),user_id:$.id,webauthn_user_id:$.email||"",counter:W.registrationInfo?.credential.counter||0,credential_type:W.registrationInfo?.credentialType||"",device_type:W.registrationInfo?.credentialDeviceType||"",backup_eligible:!1,backup_status:W.registrationInfo?.credentialBackedUp||!1,transports:JSON.stringify(["internal"]),last_used_at:O1()};await p.insertInto("passkeys").values(Q).executeTakeFirstOrThrow()}function O1(){let $=new Date,W=(Y)=>String(Y).padStart(2,"0"),Z=$.getFullYear(),J=W($.getMonth()+1),Q=W($.getDate()),G=W($.getHours()),X=W($.getMinutes()),K=W($.getSeconds());return`${Z}-${J}-${Q} ${G}:${X}:${K}`}var $W=300;async function eZ($,W,Z,J=$W){let Q=new Date(Date.now()+J*1000).toISOString();await p.deleteFrom("webauthn_challenges").where("user_id","=",$).where("purpose","=",Z).execute(),await p.insertInto("webauthn_challenges").values({user_id:$,challenge:W,purpose:Z,expires_at:Q}).execute()}async function $2($,W){let Z=await p.selectFrom("webauthn_challenges").where("user_id","=",$).where("purpose","=",W).selectAll().executeTakeFirst();if(!Z)return null;await p.deleteFrom("webauthn_challenges").where("user_id","=",$).where("purpose","=",W).execute();let J=Z.expires_at?new Date(String(Z.expires_at)).getTime():0;if(Date.now()>J)return null;return String(Z.challenge)}import{randomBytes as LW}from"crypto";import{config as m}from"@stacksjs/config";import{db as t}from"@stacksjs/database";import{mail as C0,template as H1}from"@stacksjs/email";import{log as VW}from"@stacksjs/logging";import{formatDate as FW}from"@stacksjs/orm";import{makeHash as z1,verifyHash as D1}from"@stacksjs/security";import{HttpError as p0}from"@stacksjs/error-handling";import{log as q0}from"@stacksjs/logging";import{User as _1}from"@stacksjs/orm";import{verifyHash as WW}from"@stacksjs/security";import{db as w}from"@stacksjs/database";import{getCurrentRequest as ZW}from"@stacksjs/router";function JW(){let $=crypto.getRandomValues(new Uint8Array(32));return Array.from($,(W)=>W.toString(16).padStart(2,"0")).join("")}function QW($){if($)return{ip:$.ip??null,userAgent:$.userAgent??null};let W=ZW();if(!W)return{ip:null,userAgent:null};let Z=W.headers,J=Z?.get?.("x-forwarded-for")||Z?.get?.("X-Forwarded-For")||"",Q=Z?.get?.("x-real-ip")||Z?.get?.("X-Real-IP")||"",G=(J?J.split(",")[0].trim():"")||Q||null,X=Z?.get?.("user-agent")||Z?.get?.("User-Agent")||null;return{ip:G,userAgent:X}}async function GW($,W,Z){let J=($||"").toLowerCase(),Q=await k.isRateLimited(J),G=await _1.where("email",$).first(),X=G?.password||a,K=await WW(W,X);if(Q)throw new p0(429,"Too many login attempts. Please try again later.");if(!K||!G)throw await k.recordFailedAttempt(J),new p0(401,"Invalid credentials");await k.resetAttempts(J);let Y=JW(),L=new Date(Date.now()+86400000),{ip:O,userAgent:_}=QW(Z);q0.debug(`[auth] Session created for user#${G.id}`);try{await w.insertInto("sessions").values({id:Y,user_id:G.id,ip_address:O,user_agent:_,payload:"{}",last_activity:Math.floor(Date.now()/1000),expires_at:L.toISOString()}).execute()}catch(E){throw q0.error(`[auth] Session persistence failed for user#${G.id}: ${E.message}`),new p0(500,"Session could not be created. Ensure the `sessions` table exists (run `./buddy migrate`).")}return{user:G,sessionId:Y}}async function XW($){q0.debug("[auth] Session destroyed");try{await w.deleteFrom("sessions").where("id","=",$).execute()}catch(W){q0.debug(`[auth] Session destroy failed: ${W.message}`)}}async function m0($){try{await w.deleteFrom("sessions").where("user_id","=",$).execute()}catch(W){let Z=W instanceof Error?W.message:String(W);if(Z.includes("sessions")&&/no such table|does not exist|doesn't exist/i.test(Z))return;throw W}}async function c0($){try{let W=await w.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!W)return;let Z=W.expires_at?new Date(String(W.expires_at)).getTime():0;if(Date.now()>Z){await w.deleteFrom("sessions").where("id","=",$).execute();return}return await _1.find(W.user_id)}catch{return}}async function YW($){try{let W=await w.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!W)return!1;let Z=W.expires_at?new Date(String(W.expires_at)).getTime():0;if(Date.now()>Z)return await w.deleteFrom("sessions").where("id","=",$).execute(),!1;return!0}catch{return!1}}async function KW($,W=86400000){try{let Z=await w.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!Z)return!1;let J=Z.expires_at?new Date(String(Z.expires_at)).getTime():0;if(Date.now()>J)return await w.deleteFrom("sessions").where("id","=",$).execute(),!1;let Q=new Date(Date.now()+W);return await w.updateTable("sessions").set({expires_at:Q.toISOString(),last_activity:Math.floor(Date.now()/1000)}).where("id","=",$).execute(),!0}catch{return!1}}var U2={login:GW,logout:XW,destroyAll:m0,user:c0,check:YW,refresh:KW};L0();function u0(){return m.auth.passwordReset?.expire??60}function j1($){let W=$.expires_at;if(typeof W==="string"||W instanceof Date)return new Date(W).getTime()>Date.now();let Z=$.created_at;if(typeof Z==="string"||Z instanceof Date){let J=u0();return new Date(Z).getTime()+J*60000>Date.now()}return!1}async function BW($){let W=m.app.name||"Stacks",Z=m.app.supportEmail||m.email?.from?.address||"",J=new Date().toLocaleString("en-US",{dateStyle:"full",timeStyle:"short"});try{let{html:Q,text:G}=await H1("password-changed",{subject:`Your ${W} password has been changed`,variables:{changedAt:J,supportEmail:Z}});if(!Q&&!G){await C0.send({to:$,subject:`Your ${W} password has been changed`,text:`Your ${W} password was changed on ${J}.${Z?` If this wasn't you, contact ${Z}.`:""}`});return}await C0.send({to:$,subject:`Your ${W} password has been changed`,text:G,html:Q})}catch(Q){console.error("[PasswordReset] Failed to send password changed notification:",Q)}}function y2($){function W(){return LW(32).toString("hex")}async function Z(){let X=W(),K=await z1(X,{algorithm:"bcrypt"}),Y=u0(),L=new Date(Date.now()+Y*60000).toISOString();return await t.deleteFrom("password_resets").where("email","=",$).execute(),await t.insertInto("password_resets").values({email:$,token:K,expires_at:L}).executeTakeFirst(),X}async function J(){if(!await t.selectFrom("users").where("email","=",$).selectAll().executeTakeFirst())return;let K=await Z(),Y=u0(),L=m.app.name||"Stacks",O=m.app.url?`https://${m.app.url}`:`http://localhost:${process.env.PORT||"3000"}`,E=(m.auth.passwordReset?.url??"/password/reset/{token}?email={email}").replace("{token}",K).replace("{email}",encodeURIComponent($)),F=/^https?:\/\//.test(E)?E:`${O}${E.startsWith("/")?"":"/"}${E}`;try{let{html:H,text:P}=await H1("password-reset",{subject:`Reset Your ${L} Password`,variables:{resetUrl:F,expireMinutes:Y}});if(!H&&!P)throw Error("password-reset template missing or rendered empty");await C0.send({to:$,subject:`Reset Your ${L} Password`,text:P,html:H})}catch(H){let P=H instanceof Error?H.message:String(H);console.warn(`[PasswordReset] template render failed, sending plain-text fallback: ${P}`),await C0.send({to:$,subject:`Reset Your ${L} Password`,text:`Reset your password by visiting: ${F}
162
+
163
+ This link expires in ${Y} minutes. If you didn't request this, you can safely ignore this email.`})}}async function Q(X){let K=await t.selectFrom("password_resets").where("email","=",$).selectAll().executeTakeFirst();if(!K)return!1;if(!j1(K))return await t.deleteFrom("password_resets").where("email","=",$).execute(),!1;let Y=K.token;return await D1(X,Y)}async function G(X,K){let Y=await t.transaction(async(L)=>{let O=L,_=await O.selectFrom("password_resets").where("email","=",$).selectAll().executeTakeFirst();if(!_)return{success:!1,message:"Invalid or expired reset token"};if(!j1(_))return await O.deleteFrom("password_resets").where("email","=",$).execute(),{success:!1,message:"This password reset link has expired. Please request a new one."};let E=_.token;if(!await D1(X,E))return{success:!1,message:"Invalid or expired reset token"};let H=await O.selectFrom("users").where("email","=",$).selectAll().executeTakeFirst();if(!H)return{success:!1,message:"Invalid or expired reset token"};let P=await z1(K,{algorithm:"bcrypt"});try{await O.updateTable("users").set({password:P,password_changed_at:FW(new Date)}).where("email","=",$).executeTakeFirst()}catch(T){let A=T instanceof Error?T.message:String(T);if(/password_changed_at|no such column|unknown column/i.test(A))VW.warn("[PasswordReset] password_changed_at column missing \u2014 run `buddy migrate`; resetting without the credential-version stamp"),await O.updateTable("users").set({password:P}).where("email","=",$).executeTakeFirst();else throw T}return await O.deleteFrom("password_resets").where("email","=",$).execute(),{success:!0,userId:Number(H.id)}});if(Y.success)return await j0(Y.userId),await m0(Y.userId),BW($).catch((L)=>{console.error("[PasswordReset] Failed to send notification:",L)}),{success:!0};return Y}return{sendEmail:J,verifyToken:Q,resetPassword:G}}import{db as _W}from"@stacksjs/database";import{HttpError as P0}from"@stacksjs/error-handling";import{User as zW}from"@stacksjs/orm";import{makeHash as DW}from"@stacksjs/security";import{db as D}from"@stacksjs/database";import{isUniqueViolation as t0}from"@stacksjs/orm";function i0($){if(!t0($))throw $}function I($){if(!$)return null;return{id:Number($.id),name:String($.name),guard_name:String($.guard_name),description:$.description==null?void 0:String($.description),created_at:$.created_at==null?void 0:String($.created_at),updated_at:$.updated_at==null?void 0:String($.updated_at)}}async function M1($,W,Z,J){await D.insertInto($).values({name:W,guard_name:Z,description:J??null}).execute();let Q=await D.selectFrom($).selectAll().where("name","=",W).where("guard_name","=",Z).orderBy("id","desc").limit(1).executeTakeFirst(),G=I(Q);if(!G)throw Error(`[rbac] insertAndFetch(${$}, ${W}, ${Z}) succeeded but follow-up SELECT returned nothing`);return G}async function l0($,W){let Z=D.selectFrom($).select("user_id"in W?"user_id":"role_id");for(let[Q,G]of Object.entries(W))Z=Z.where(Q,"=",G);if(await Z.limit(1).executeTakeFirst())return;try{await D.insertInto($).values(W).execute()}catch(Q){i0(Q)}}async function d0($,W){let Z=D.deleteFrom($);for(let[J,Q]of Object.entries(W))Z=Z.where(J,"=",Q);await Z.execute()}async function U1($,W,Z){await D.deleteFrom($).where(W,"=",Z).execute()}async function n0($,W,Z,J,Q){let G=Array.from(new Set(Q));await D.transaction(async(X)=>{let K=X;if(await K.deleteFrom($).where(W,"=",Z).execute(),G.length===0)return;let Y=G.map((L)=>({[W]:Z,[J]:L}));try{await K.insertInto($).values(Y).execute()}catch(L){i0(L);for(let O of G)try{await K.insertInto($).values({[W]:Z,[J]:O}).execute()}catch(_){i0(_)}}})}function OW(){return{async findRoleByName($,W="web"){let Z=await D.selectFrom("roles").selectAll().where("name","=",$).where("guard_name","=",W).limit(1).executeTakeFirst();return I(Z)},async findRoleById($){let W=await D.selectFrom("roles").selectAll().where("id","=",$).limit(1).executeTakeFirst();return I(W)},async createRole($,W="web",Z){return await M1("roles",$,W,Z)},async deleteRole($){await D.deleteFrom("user_roles").where("role_id","=",$).execute(),await D.deleteFrom("role_permissions").where("role_id","=",$).execute(),await D.deleteFrom("roles").where("id","=",$).execute()},async getAllRoles($){let W=D.selectFrom("roles").selectAll();if($)W=W.where("guard_name","=",$);return(await W.orderBy("id","asc").execute()).map((J)=>I(J)).filter(Boolean)},async findPermissionByName($,W="web"){let Z=await D.selectFrom("permissions").selectAll().where("name","=",$).where("guard_name","=",W).limit(1).executeTakeFirst();return I(Z)},async findPermissionById($){let W=await D.selectFrom("permissions").selectAll().where("id","=",$).limit(1).executeTakeFirst();return I(W)},async createPermission($,W="web",Z){return await M1("permissions",$,W,Z)},async deletePermission($){await D.deleteFrom("user_permissions").where("permission_id","=",$).execute(),await D.deleteFrom("role_permissions").where("permission_id","=",$).execute(),await D.deleteFrom("permissions").where("id","=",$).execute()},async getAllPermissions($){let W=D.selectFrom("permissions").selectAll();if($)W=W.where("guard_name","=",$);return(await W.orderBy("id","asc").execute()).map((J)=>I(J)).filter(Boolean)},async getUserRoles($){return(await D.selectFrom("roles").innerJoin("user_roles","user_roles.role_id","=","roles.id").select(["roles.id as id","roles.name as name","roles.guard_name as guard_name","roles.description as description","roles.created_at as created_at","roles.updated_at as updated_at"]).where("user_roles.user_id","=",$).orderBy("roles.id","asc").execute()).map((Z)=>I(Z)).filter(Boolean)},assignRoleToUser($,W){return l0("user_roles",{user_id:$,role_id:W})},removeRoleFromUser($,W){return d0("user_roles",{user_id:$,role_id:W})},removeAllRolesFromUser($){return U1("user_roles","user_id",$)},syncUserRoles($,W){return n0("user_roles","user_id",$,"role_id",W)},async getUserDirectPermissions($){return(await D.selectFrom("permissions").innerJoin("user_permissions","user_permissions.permission_id","=","permissions.id").select(["permissions.id as id","permissions.name as name","permissions.guard_name as guard_name","permissions.description as description","permissions.created_at as created_at","permissions.updated_at as updated_at"]).where("user_permissions.user_id","=",$).orderBy("permissions.id","asc").execute()).map((Z)=>I(Z)).filter(Boolean)},assignPermissionToUser($,W){return l0("user_permissions",{user_id:$,permission_id:W})},removePermissionFromUser($,W){return d0("user_permissions",{user_id:$,permission_id:W})},removeAllPermissionsFromUser($){return U1("user_permissions","user_id",$)},syncUserPermissions($,W){return n0("user_permissions","user_id",$,"permission_id",W)},async getRolePermissions($){return(await D.selectFrom("permissions").innerJoin("role_permissions","role_permissions.permission_id","=","permissions.id").select(["permissions.id as id","permissions.name as name","permissions.guard_name as guard_name","permissions.description as description","permissions.created_at as created_at","permissions.updated_at as updated_at"]).where("role_permissions.role_id","=",$).orderBy("permissions.id","asc").execute()).map((Z)=>I(Z)).filter(Boolean)},assignPermissionToRole($,W){return l0("role_permissions",{role_id:$,permission_id:W})},removePermissionFromRole($,W){return d0("role_permissions",{role_id:$,permission_id:W})},syncRolePermissions($,W){return n0("role_permissions","role_id",$,"permission_id",W)}}}var jW=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;async function c2($){let{email:W,password:Z,name:J}=$;if(typeof W!=="string"||W.length>254||!jW.test(W))throw new P0(422,"Email address is invalid");if(typeof Z!=="string"||Z.length<8)throw new P0(422,"Password must be at least 8 characters");let Q=await DW(Z,{algorithm:"bcrypt"}),G=await _W.transaction(async(K)=>{let Y=K;if(await Y.selectFrom("users").where("email","=",W).selectAll().executeTakeFirst())throw new P0(409,"Email already exists");try{await Y.insertInto("users").values({email:W,password:Q,name:J}).execute()}catch(_){if(t0(_))throw new P0(409,"Email already exists");throw _}let O=await Y.selectFrom("users").where("email","=",W).selectAll().executeTakeFirst();if(!O)throw Error("Failed to retrieve created user");return Number(O.id)}),X=await zW.find(G);if(!X)throw Error("Failed to retrieve created user");return{token:await R.createToken(X,"user-auth-token")}}import{request as $0}from"@stacksjs/router";async function F0(){let $=$0?._authenticatedUser;if($)return $;let W=$0.bearerToken?.();if(!W){let Z=$0.headers?.get?.("authorization")||$0.headers?.get?.("Authorization");if(Z&&Z.startsWith("Bearer "))W=Z.substring(7)}if(!W)return;return await R.getUserFromToken(W)}async function n2(){return F0()}async function HW(){return!!await F0()}async function i2(){return(await F0())?.id}async function t2(){return(await F0())?.email}async function o2(){return(await F0())?.name}async function a2(){return await HW()}async function r2(){await R.logout()}async function s2(){if($0?._authenticatedUser)$0._authenticatedUser=void 0}L0();J0();J0();J0();import{log as c}from"@stacksjs/logging";import*as R0 from"@stacksjs/path";class UW{allow($){return q.allow($)}deny($,W){return q.deny($,W)}denyIf($,W){if($)return this.deny(W);return!0}denyUnless($,W){if(!$)return this.deny(W);return!0}allowIf($,W){if($)return this.allow(W);return!1}}async function qW(){let{fs:$}=await import("@stacksjs/storage"),W=R0.appPath("Policies");if(!$.existsSync(W)){c.debug("No Policies directory found");return}try{let G=(await import(R0.appPath("Gates.ts"))).policies||{};for(let[X,K]of Object.entries(G)){let Y=typeof K==="string"?K:K.policy,L=`${W}/${Y}.ts`;if($.existsSync(L)){let O=await import(L),_=O.default||O[Y];if(_)O0(X,_),c.debug(`Registered policy: ${Y} for ${X}`)}}}catch{c.debug("No Gates.ts found, using convention-based discovery")}let Z=$.readdirSync(W).filter((J)=>J.endsWith("Policy.ts"));for(let J of Z){let Q=J.replace(".ts",""),G=Q.replace("Policy",""),X=`${W}/${J}`;try{let K=await import(X),Y=K.default||K[Q];if(Y)O0(G,Y),c.debug(`Auto-discovered policy: ${Q} for ${G}`)}catch(K){c.error(`Failed to load policy ${Q}:`,K)}}}async function CW(){let{define:$,before:W,after:Z}=await Promise.resolve().then(() => (J0(),w1));try{let Q=await import(R0.appPath("Gates.ts")),G=Q.gates||Q.default?.gates||{};for(let[Y,L]of Object.entries(G))if(typeof L==="function")$(Y,L),c.debug(`Registered gate: ${Y}`);let X=Q.before||Q.default?.before||[];for(let Y of X)if(typeof Y==="function")W(Y);let K=Q.after||Q.default?.after||[];for(let Y of K)if(typeof Y==="function")Z(Y);c.debug("Gates registered successfully")}catch{c.debug("No Gates.ts found or failed to load")}}async function J3(){await CW(),await qW()}J0();async function PW($,W,...Z){return v0(W,$,...Z)}async function vW($,W,...Z){return S0(W,$,...Z)}async function SW($,W,...Z){return _0(W,$,...Z)}async function EW($,W,...Z){return E0(W,$,...Z)}async function xW($,W,...Z){return x0(W,$,...Z)}async function X3($,W,...Z){return W0(W,$,...Z)}function Y3($){return Object.assign($,{can:(Z,...J)=>PW($,Z,...J),cannot:(Z,...J)=>vW($,Z,...J),canAny:(Z,...J)=>SW($,Z,...J),canAll:(Z,...J)=>EW($,Z,...J),authorize:(Z,...J)=>xW($,Z,...J)})}class A0{max;map=new Map;constructor($){this.max=$}get($){return this.map.get($)}has($){return this.map.has($)}set($,W){if(this.map.has($))this.map.delete($);if(this.map.set($,W),this.map.size>this.max){let Z=this.map.keys().next().value;if(Z!==void 0)this.map.delete(Z)}return this}delete($){return this.map.delete($)}clear(){this.map.clear()}}var o0=1e4,V={userRoles:new A0(o0),userPermissions:new A0(o0),rolePermissions:new A0(o0),roles:new Map,permissions:new Map},a0=null;function RW($){a0=$,N0()}function j(){if(!a0)throw Error("RBAC store not configured. Call setRbacStore() first.");return a0}function N0(){V.userRoles.clear(),V.userPermissions.clear(),V.rolePermissions.clear(),V.roles.clear(),V.permissions.clear()}function N($){if(typeof $==="number"){if(!Number.isFinite($)||$<=0)throw TypeError("RBAC user id must be a positive number");return $}let W=$.id;if(typeof W!=="number"||!Number.isFinite(W)||W<=0)throw TypeError(`RBAC user id must be a positive number, got ${typeof W} (${String(W)})`);return W}async function r0($,W="web",Z){let J=await j().createRole($,W,Z);return V.roles.set(`${$}:${W}`,J),J}async function b($,W="web"){let Z=`${$}:${W}`;if(V.roles.has(Z))return V.roles.get(Z);let J=await j().findRoleByName($,W);if(J)V.roles.set(Z,J);return J}async function AW($,W="web"){let Z=await b($,W);if(Z)await j().deleteRole(Z.id),V.roles.delete(`${$}:${W}`),N0()}async function NW($){return j().getAllRoles($)}async function yW($,W="web",Z){let J=await j().createPermission($,W,Z);return V.permissions.set(`${$}:${W}`,J),J}async function u($,W="web"){let Z=`${$}:${W}`;if(V.permissions.has(Z))return V.permissions.get(Z);let J=await j().findPermissionByName($,W);if(J)V.permissions.set(Z,J);return J}async function TW($,W="web"){let Z=await u($,W);if(Z)await j().deletePermission(Z.id),V.permissions.delete(`${$}:${W}`),N0()}async function fW($){return j().getAllPermissions($)}async function Q0($){let W=N($);if(V.userRoles.has(W))return V.userRoles.get(W);let Z=await j().getUserRoles(W);return V.userRoles.set(W,Z),Z}async function I1($,W,Z="web"){let J=N($),Q=await b(W,Z);if(!Q)throw Error(`Role '${W}' not found.`);await j().assignRoleToUser(J,Q.id),V.userRoles.delete(J),V.userPermissions.delete(J)}async function b1($,W,Z="web"){let J=N($),Q=await b(W,Z);if(!Q)return;await j().removeRoleFromUser(J,Q.id),V.userRoles.delete(J),V.userPermissions.delete(J)}async function wW($){let W=N($);await j().removeAllRolesFromUser(W),V.userRoles.delete(W),V.userPermissions.delete(W)}async function k1($,W,Z="web"){let J=N($),Q=[];for(let G of W){let X=await b(G,Z);if(!X)throw Error(`Role '${G}' not found.`);Q.push(X.id)}await j().syncUserRoles(J,Q),V.userRoles.delete(J),V.userPermissions.delete(J)}async function g1($,W,Z="web"){return(await Q0($)).some((Q)=>Q.name===W&&Q.guard_name===Z)}async function h1($,W,Z="web"){let J=await Q0($);return W.some((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function p1($,W,Z="web"){let J=await Q0($);return W.every((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function z0($){let W=N($);if(V.userPermissions.has(W))return V.userPermissions.get(W);let Z=await j().getUserDirectPermissions(W),J=await Q0($),Q=[];for(let K of J){let Y=await i1(K.id);Q.push(...Y)}let G=new Set,X=[];for(let K of[...Z,...Q])if(!G.has(K.id))G.add(K.id),X.push(K);return V.userPermissions.set(W,X),X}async function m1($,W,Z="web"){let J=N($),Q=await u(W,Z);if(!Q)throw Error(`Permission '${W}' not found.`);await j().assignPermissionToUser(J,Q.id),V.userPermissions.delete(J)}async function c1($,W,Z="web"){let J=N($),Q=await u(W,Z);if(!Q)return;await j().removePermissionFromUser(J,Q.id),V.userPermissions.delete(J)}async function IW($){let W=N($);await j().removeAllPermissionsFromUser(W),V.userPermissions.delete(W)}async function u1($,W,Z="web"){let J=N($),Q=[];for(let G of W){let X=await u(G,Z);if(!X)throw Error(`Permission '${G}' not found.`);Q.push(X.id)}await j().syncUserPermissions(J,Q),V.userPermissions.delete(J)}async function l1($,W,Z="web"){return(await z0($)).some((Q)=>Q.name===W&&Q.guard_name===Z)}async function d1($,W,Z="web"){let J=await z0($);return W.some((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function n1($,W,Z="web"){let J=await z0($);return W.every((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function i1($){if(V.rolePermissions.has($))return V.rolePermissions.get($);let W=await j().getRolePermissions($);return V.rolePermissions.set($,W),W}async function bW($,W,Z="web"){let J=await b($,Z);if(!J)throw Error(`Role '${$}' not found.`);let Q=await u(W,Z);if(!Q)throw Error(`Permission '${W}' not found.`);await j().assignPermissionToRole(J.id,Q.id),V.rolePermissions.delete(J.id),V.userPermissions.clear()}async function kW($,W,Z="web"){let J=await b($,Z);if(!J)return;let Q=await u(W,Z);if(!Q)return;await j().removePermissionFromRole(J.id,Q.id),V.rolePermissions.delete(J.id),V.userPermissions.clear()}async function gW($,W,Z="web"){let J=await b($,Z);if(!J)throw Error(`Role '${$}' not found.`);let Q=[];for(let G of W){let X=await u(G,Z);if(!X)throw Error(`Permission '${G}' not found.`);Q.push(X.id)}await j().syncRolePermissions(J.id,Q),V.rolePermissions.delete(J.id),V.userPermissions.clear()}function hW($){let W=N($);return Object.assign($,{hasRole:(J,Q)=>g1(W,J,Q),hasAnyRole:(J,Q)=>h1(W,J,Q),hasAllRoles:(J,Q)=>p1(W,J,Q),hasPermission:(J,Q)=>l1(W,J,Q),hasAnyPermission:(J,Q)=>d1(W,J,Q),hasAllPermissions:(J,Q)=>n1(W,J,Q),getRoles:()=>Q0(W),getPermissions:()=>z0(W),assignRole:(J,Q)=>I1(W,J,Q),removeRole:(J,Q)=>b1(W,J,Q),syncRoles:(J,Q)=>k1(W,J,Q),givePermission:(J,Q)=>m1(W,J,Q),revokePermission:(J,Q)=>c1(W,J,Q),syncPermissions:(J,Q)=>u1(W,J,Q)})}var L3={setStore:RW,flushCache:N0,createRole:r0,findRole:b,deleteRole:AW,getAllRoles:NW,createPermission:yW,findPermission:u,deletePermission:TW,getAllPermissions:fW,getUserRoles:Q0,assignRole:I1,removeRole:b1,removeAllRoles:wW,syncRoles:k1,hasRole:g1,hasAnyRole:h1,hasAllRoles:p1,getUserPermissions:z0,givePermission:m1,revokePermission:c1,revokeAllPermissions:IW,syncPermissions:u1,hasPermission:l1,hasAnyPermission:d1,hasAllPermissions:n1,getRolePermissions:i1,givePermissionToRole:bW,revokePermissionFromRole:kW,syncRolePermissions:gW,withRbac:hW};var t1=[{name:"admin",guard_name:"web",description:"Full access. Sees every dashboard surface, every model, every infra control."},{name:"dev",guard_name:"web",description:"Developer / infra. Sees dev-mode surfaces (CI, query inspector, runner alerts) but not billing/admin-only management."},{name:"client",guard_name:"web",description:"End user / client. Sees content, orders, profile, billing \u2014 no dev tools, no infra surfaces."}];async function pW(){let $=[],W=[];for(let Z of t1){if(await b(Z.name,Z.guard_name)){W.push({name:Z.name,guard_name:Z.guard_name,reason:"already_exists"});continue}let Q=await r0(Z.name,Z.guard_name,Z.description);$.push(Q)}return{created:$,skipped:W}}import{Buffer as o1}from"buffer";import{createHmac as r1,randomBytes as mW,timingSafeEqual as cW}from"crypto";import{config as G0}from"@stacksjs/config";import{db as o}from"@stacksjs/database";import{mail as a1,template as uW}from"@stacksjs/email";import{log as lW}from"@stacksjs/logging";function s1(){let $=G0.app.key;if(typeof $!=="string"||$.length===0)throw Error("[auth] config.app.key is not set \u2014 email-verification HMAC requires a real APP_KEY. "+"Run `./buddy key:generate` to provision one, or set the APP_KEY env var before booting the app.");return $}function dW($){let W=mW(32).toString("hex"),Z=`${$}:${W}`,J=r1("sha256",s1()).update(Z).digest("hex");return{token:W,hash:J}}function nW($,W,Z){let J=`${$}:${W}`,Q=r1("sha256",s1()).update(J).digest("hex"),G=o1.from(Q),X=o1.from(Z);if(G.length!==X.length)return!1;return cW(G,X)}function iW(){let W=(G0.auth??{}).emailVerification;if(W!=null&&typeof W==="object"){let Z=W;if(typeof Z.expire==="number")return Z.expire}return 60}function tW($,W){let Z=G0.app.url?`https://${G0.app.url}`:`http://localhost:${process.env.PORT||"3000"}`,Q=(G0.auth.emailVerification?.url??"/verify-email/{id}/{token}").replace("{id}",String($)).replace("{token}",W);return/^https?:\/\//.test(Q)?Q:`${Z}${Q.startsWith("/")?"":"/"}${Q}`}function e1($){return $.email_verified_at!=null}async function $$($){let{token:W,hash:Z}=dW($.id),J=iW(),Q=new Date(Date.now()+J*60*1000);await o.deleteFrom("email_verifications").where("user_id","=",$.id).execute(),await o.insertInto("email_verifications").values({user_id:$.id,token:Z,expires_at:Q.toISOString()}).executeTakeFirst();let G=tW($.id,W),X=G0.app.name||"Stacks";try{let{html:K,text:Y}=await uW("email-verification",{subject:`Verify Your ${X} Email Address`,variables:{verificationUrl:G,expiryMinutes:J,userName:$.name||$.email}});if(!K&&!Y)throw Error("email-verification template missing or rendered empty");await a1.send({to:$.email,subject:`Verify Your ${X} Email Address`,text:Y,html:K})}catch(K){let Y=K instanceof Error?K.message:String(K);lW.warn(`[email] Email verification template failed, using plain text fallback: ${Y}`),await a1.send({to:$.email,subject:`Verify Your ${X} Email Address`,text:`Please verify your email address by visiting: ${G}
156
164
 
157
- This link expires in ${X} minutes.`})}}async function b5($,Z){let Q=await p.selectFrom("email_verifications").where("user_id","=",$).selectAll().executeTakeFirst();if(!Q)return{success:!1,message:"No verification request found. Please request a new verification email."};let X=new Date(Q.expires_at);if(new Date>X)return await p.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!1,message:"Verification link has expired. Please request a new one."};if(!T5($,Z,Q.token))return{success:!1,message:"Invalid verification link."};return await p.updateTable("users").set({email_verified_at:new Date().toISOString()}).where("id","=",$).executeTakeFirst(),await p.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!0,message:"Email verified successfully."}}async function g5($){if(O4($))return{success:!1,message:"Email is already verified."};let Z=await p.selectFrom("email_verifications").where("user_id","=",$.id).selectAll().executeTakeFirst();if(Z){let Q=new Date(Z.created_at),X=(Date.now()-Q.getTime())/1000;if(X<60)return{success:!1,message:`Please wait ${Math.ceil(60-X)} seconds before requesting another verification email.`}}return await D4($),{success:!0,message:"Verification email sent."}}var Y8={isVerified:O4,send:D4,verify:b5,resend:g5};import{HttpError as k5}from"@stacksjs/error-handling";import{log as D0}from"@stacksjs/logging";import{User as q4}from"@stacksjs/orm";import{verifyHash as h5}from"@stacksjs/security";import{db as I}from"@stacksjs/database";var p5="$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";function m5(){let $=crypto.getRandomValues(new Uint8Array(32));return Array.from($,(Z)=>Z.toString(16).padStart(2,"0")).join("")}async function u5($,Z){let Q=await q4.where("email",$).first(),X=Q?.password||p5;if(!await h5(Z,X)||!Q)throw new k5(401,"Invalid credentials");let _=m5(),W=new Date(Date.now()+86400000);D0.debug(`[auth] Session created for user#${Q.id}`);try{await I.insertInto("sessions").values({id:_,user_id:Q.id,ip_address:null,user_agent:null,payload:"{}",last_activity:Math.floor(Date.now()/1000),expires_at:W.toISOString()}).execute()}catch(J){D0.debug(`[auth] Sessions table not available, session is memory-only. err=${J.message}`)}return{user:Q,sessionId:_}}async function c5($){D0.debug("[auth] Session destroyed");try{await I.deleteFrom("sessions").where("id","=",$).execute()}catch(Z){D0.debug(`[auth] Session destroy failed: ${Z.message}`)}}async function l5($){try{let Z=await I.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!Z)return;let Q=Z.expires_at?new Date(String(Z.expires_at)).getTime():0;if(Date.now()>Q){await I.deleteFrom("sessions").where("id","=",$).execute();return}return await q4.find(Z.user_id)}catch{return}}async function d5($){try{let Z=await I.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!Z)return!1;let Q=Z.expires_at?new Date(String(Z.expires_at)).getTime():0;if(Date.now()>Q)return await I.deleteFrom("sessions").where("id","=",$).execute(),!1;return!0}catch{return!1}}async function n5($,Z=86400000){try{let Q=await I.selectFrom("sessions").where("id","=",$).selectAll().executeTakeFirst();if(!Q)return!1;let X=Q.expires_at?new Date(String(Q.expires_at)).getTime():0;if(Date.now()>X)return await I.deleteFrom("sessions").where("id","=",$).execute(),!1;let Y=new Date(Date.now()+Z);return await I.updateTable("sessions").set({expires_at:Y.toISOString(),last_activity:Math.floor(Date.now()/1000)}).where("id","=",$).execute(),!0}catch{return!1}}var K8={login:u5,logout:c5,user:l5,check:d5,refresh:n5};import{generateTOTP as N8,verifyTOTP as y8,generateTOTPSecret as I8,totpKeyUri as T8}from"@stacksjs/ts-auth";export{A5 as withRbac,t7 as withAuthorization,l6 as verifyTwoFactorCode,y8 as verifyTOTP,F7 as verifyRegistrationResponse,b5 as verifyEmail,K7 as verifyAuthenticationResponse,D6 as validateRefreshToken,V5 as userCannot,B5 as userCanAny,O5 as userCanAll,K5 as userCan,T8 as totpKeyUri,J6 as tokens,L6 as tokenCant,K6 as tokenCanAny,F6 as tokenCanAll,h4 as tokenCan,V6 as tokenAbilities,G6 as token,$4 as syncRoles,P5 as syncRolePermissions,_4 as syncPermissions,V7 as startRegistration,B7 as startAuthentication,q5 as setRbacStore,W7 as setCurrentRegistrationOptions,l5 as sessionUser,n5 as sessionRefresh,c5 as sessionLogout,u5 as sessionLogin,d5 as sessionCheck,D4 as sendVerificationEmail,z6 as revokeTokenById,M6 as revokeToken,q6 as revokeRefreshToken,x5 as revokePermissionFromRole,W4 as revokePermission,H6 as revokeOtherTokens,P6 as revokeClient,m4 as revokeAllTokens,p4 as revokeAllRefreshTokens,E5 as revokeAllPermissions,g5 as resendVerificationEmail,e0 as removeRole,v5 as removeAllRoles,F5 as registerGates,R7 as register,O6 as refreshToken,h7 as refresh,X0 as policy,q7 as platformAuthenticatorIsAvailable,U7 as passwordResets,y as parseScopes,l0 as none,b7 as name,k7 as logout,O4 as isEmailVerified,g7 as isAuthenticated,i7 as inspectUser,n as inspect,l7 as initializeAuthorization,T7 as id,Z4 as hasRole,i0 as hasPolicy,J4 as hasPermission,Q4 as hasAnyRole,G4 as hasAnyPermission,X4 as hasAllRoles,L4 as hasAllPermissions,n0 as has,S5 as givePermissionToRole,Y4 as givePermission,o as getUserRoles,W0 as getUserPermissions,X7 as getUserPasskeys,Y7 as getUserPasskey,F4 as getRolePermissions,d0 as getPolicyFor,I7 as getCurrentUser,M5 as getAllRoles,U5 as getAllPermissions,d6 as generateTwoFactorUri,c6 as generateTwoFactorToken,t4 as generateTwoFactorSecret,I8 as generateTOTPSecret,N8 as generateTOTP,G7 as generateRegistrationOptions,L7 as generateAuthenticationOptions,O0 as flushRbacCache,o0 as flush,k4 as findToken,b as findRole,g as findPermission,S6 as findClient,f7 as email,L5 as discoverPolicies,c0 as denies,j5 as deleteRole,v6 as deleteRevokedTokens,j6 as deleteRevokedRefreshTokens,H5 as deletePermission,U6 as deleteExpiredTokens,C6 as deleteExpiredRefreshTokens,h0 as define,u as currentAccessToken,B6 as createToken,C5 as createRole,r6 as createPersonalAccessClient,z5 as createPermission,x6 as createClient,E6 as clients,_5 as check,F0 as cannot,L0 as can,D7 as browserSupportsWebAuthnAutofill,O7 as browserSupportsWebAuthn,p0 as before,D5 as authorizeUser,V0 as authorize,Z0 as authUser,$7 as authMiddlewareHandler,e4 as authMiddleware,s0 as assignRole,Y0 as any,u0 as allows,K0 as all,m0 as after,t0 as abilities,K8 as SessionAuth,a7 as Rbac,r as RateLimiter,a0 as Gate,Y8 as EmailVerification,G5 as BasePolicy,z as AuthorizationResponse,Q0 as AuthorizationException,R as Auth};
165
+ This link expires in ${J} minutes.`})}}async function oW($,W){let Z=await o.selectFrom("email_verifications").where("user_id","=",$).selectAll().executeTakeFirst();if(!Z)return{success:!1,message:"No verification request found. Please request a new verification email."};let J=new Date(Z.expires_at);if(new Date>J)return await o.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!1,message:"Verification link has expired. Please request a new one."};if(!nW($,W,Z.token))return{success:!1,message:"Invalid verification link."};return await o.updateTable("users").set({email_verified_at:new Date().toISOString()}).where("id","=",$).executeTakeFirst(),await o.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!0,message:"Email verified successfully."}}async function aW($){if(e1($))return{success:!1,message:"Email is already verified."};let W=await o.selectFrom("email_verifications").where("user_id","=",$.id).selectAll().executeTakeFirst();if(W){let Z=new Date(W.created_at),J=(Date.now()-Z.getTime())/1000;if(J<60)return{success:!1,message:`Please wait ${Math.ceil(60-J)} seconds before requesting another verification email.`}}return await $$($),{success:!0,message:"Verification email sent."}}var M3={isVerified:e1,send:$$,verify:oW,resend:aW};import{generateTOTP as W4,verifyTOTP as Z4,generateTOTPSecret as J4,totpKeyUri as Q4}from"@stacksjs/ts-auth";import{randomBytes as rW}from"crypto";import{db as y}from"@stacksjs/database";var sW=300;async function W$($){let W=await y.selectFrom("users").where("id","=",$).select(["two_factor_secret","two_factor_enabled"]).executeTakeFirst();return{secret:W?.two_factor_secret??null,enabled:Boolean(W?.two_factor_enabled)}}function eW($){return Boolean($.two_factor_enabled)}function $Z($,W){let Z=g0(),J=B1($,W,Z);return{secret:Z,uri:J}}var WZ=600;async function ZZ($,W,Z=WZ){let J=new Date(Date.now()+Z*1000).toISOString();await y.deleteFrom("two_factor_pending_secrets").where("user_id","=",$).execute(),await y.insertInto("two_factor_pending_secrets").values({user_id:$,secret:W,expires_at:J}).execute()}async function JZ($){let W=await y.selectFrom("two_factor_pending_secrets").where("user_id","=",$).selectAll().executeTakeFirst();if(!W)return null;await y.deleteFrom("two_factor_pending_secrets").where("user_id","=",$).execute();let Z=W.expires_at?new Date(String(W.expires_at)).getTime():0;if(Date.now()>Z)return null;return String(W.secret)}async function QZ($,W,Z){if(!await h0(Z,W))return!1;return await y.updateTable("users").set({two_factor_secret:W,two_factor_enabled:!0}).where("id","=",$).executeTakeFirst(),!0}async function GZ($){await y.updateTable("users").set({two_factor_secret:null,two_factor_enabled:!1}).where("id","=",$).executeTakeFirst()}async function XZ($,W){let{secret:Z,enabled:J}=await W$($);if(!J||!Z)return!1;return h0(W,Z)}async function YZ($,W=sW){let Z=rW(32).toString("hex"),J=new Date(Date.now()+W*1000).toISOString();return await y.deleteFrom("two_factor_challenges").where("user_id","=",$).execute(),await y.insertInto("two_factor_challenges").values({id:Z,user_id:$,expires_at:J}).execute(),Z}async function KZ($){let W=await y.selectFrom("two_factor_challenges").where("id","=",$).selectAll().executeTakeFirst();if(!W)return null;await y.deleteFrom("two_factor_challenges").where("id","=",$).execute();let Z=W.expires_at?new Date(String(W.expires_at)).getTime():0;if(Date.now()>Z)return null;return Number(W.user_id)}var v3={isEnabled:eW,getState:W$,generateSetup:$Z,stashPendingSecret:ZZ,consumePendingSecret:JZ,enable:QZ,disable:GZ,verifyLoginCode:XZ,createChallenge:YZ,consumeChallenge:KZ};import{config as l}from"@stacksjs/config";import{db as y0}from"@stacksjs/database";var s0="active_team";function Z$($){let W=$.rolePriority??{owner:0,admin:1},Z=[...$.memberships??[]].sort((G,X)=>(W[G.role??""]??2)-(W[X.role??""]??2)),J=$.activeTeamId==null?null:Number($.activeTeamId);if(J!=null&&Number.isFinite(J)){let G=Z.find((X)=>Number(X.team_id)===J);if(G)return{teamId:J,role:G.role??null};if($.allowAnyTeam)return{teamId:J,role:"admin"}}let Q=Z[0];return Q?{teamId:Number(Q.team_id),role:Q.role??null}:{teamId:null,role:null}}function J$($){let W=$.cookies?.get(s0);if(!W)return null;let Z=Number(W);return Number.isFinite(Z)&&Z>0?Z:null}function N3($,W={}){let Z=Math.max(0,Math.floor(W.maxAgeSeconds??31536000)),J=[`${s0}=${encodeURIComponent(String($))}`,"Path=/","HttpOnly","SameSite=Lax",`Max-Age=${Z}`];if(W.secure)J.push("Secure");return J.join("; ")}function y3(){return`${s0}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`}async function LZ($){let W=await VZ($);if(!W?.id)return null;let Z=await y0.selectFrom("team_members").where("user_id","=",W.id).where("status","=","active").select(["team_id","role"]).execute();if(Z.length===0)return null;let J=Z$({memberships:Z.map((Q)=>({team_id:Number(Q.team_id),role:String(Q.role)})),activeTeamId:J$($)});return J.teamId!=null?{teamId:J.teamId,role:J.role??String(Z[0].role)}:null}async function T3($,W={}){let Z={user:null,teamId:null,role:null,teams:[],activeTeamId:null},J=l.auth?.default||"api",G=(l.auth?.guards?.[J]?.driver||"token")==="session"?await Q$($):await G$($);if(!G?.id)return Z;let X=await y0.selectFrom("team_members").where("user_id","=",G.id).where("status","=","active").select(["team_id","role"]).execute(),K=W.allowAnyTeam?!!W.allowAnyTeam(G):!1,Y=J$($),L=Z$({memberships:X.map((F)=>({team_id:Number(F.team_id),role:String(F.role)})),activeTeamId:Y,allowAnyTeam:K}),O=new Map(X.map((F)=>[Number(F.team_id),String(F.role)])),_=[];if(K)_=await y0.selectFrom("teams").select(["id","name"]).execute();else{let F=[...O.keys()];_=F.length?await y0.selectFrom("teams").whereIn("id",F).select(["id","name"]).execute():[]}let E=_.map((F)=>({id:Number(F.id),name:String(F.name),role:O.get(Number(F.id))||"viewer"})).sort((F,H)=>F.name.localeCompare(H.name));return{user:G,teamId:L.teamId,role:L.role,teams:E,activeTeamId:Y}}async function f3($){let W=await LZ($);return W?W.teamId:null}async function VZ($){let W=l.auth?.default||"api",Q=((l.auth?.guards?.[W]||{driver:"token"}).driver||"token")==="session"?await Q$($):await G$($);return Q?.id?Q:void 0}async function Q$($){let W=$.cookies?.get("session_id");if(!W)return;return c0(W)}async function G$($){let W=l.auth?.defaultTokenName||"auth-token",Z=(typeof $.bearerToken==="function"?$.bearerToken():void 0)??$.cookies?.get(W);if(!Z)return;return R.getUserFromToken(Z)}export{hW as withRbac,Y3 as withAuthorization,XZ as verifyTwoFactorLoginCode,h0 as verifyTwoFactorCode,Z4 as verifyTOTP,G2 as verifyRegistrationResponse,oW as verifyEmail,X2 as verifyAuthenticationResponse,E$ as validateRefreshToken,vW as userCannot,SW as userCanAny,EW as userCanAll,PW as userCan,rZ as updatePasskeyCounter,Q4 as totpKeyUri,M$ as tokens,q$ as tokenCant,P$ as tokenCanAny,C$ as tokenCanAll,X1 as tokenCan,v$ as tokenAbilities,U$ as token,k1 as syncRoles,gW as syncRolePermissions,u1 as syncPermissions,eZ as storeWebAuthnChallenge,ZZ as stashPendingTwoFactorSecret,Y2 as startRegistration,K2 as startAuthentication,RW as setRbacStore,sZ as setCurrentRegistrationOptions,c0 as sessionUser,KW as sessionRefresh,XW as sessionLogout,GW as sessionLogin,m0 as sessionDestroyAll,YW as sessionCheck,$$ as sendVerificationEmail,Z$ as selectActiveTeam,pW as seedDefaultRoles,y$ as revokeTokenById,N$ as revokeToken,x$ as revokeRefreshToken,kW as revokePermissionFromRole,c1 as revokePermission,T$ as revokeOtherTokens,g$ as revokeClient,j0 as revokeAllTokens,Y1 as revokeAllRefreshTokens,IW as revokeAllPermissions,T3 as resolveTeamContext,VZ as resolveAuthenticatedUser,f3 as resolveAuthenticatedTeamId,LZ as resolveAuthenticatedMembership,aW as resendVerificationEmail,b1 as removeRole,wW as removeAllRoles,CW as registerGates,c2 as register,S$ as refreshToken,s2 as refresh,O0 as policy,F2 as platformAuthenticatorIsAvailable,y2 as passwordResets,f as parseScopes,x1 as none,o2 as name,r2 as logout,eW as isTwoFactorEnabled,d as isIssuedBeforePasswordChange,e1 as isEmailVerified,a2 as isAuthenticated,X3 as inspectUser,W0 as inspect,J3 as initializeAuthorization,i2 as id,g1 as hasRole,N1 as hasPolicy,l1 as hasPermission,h1 as hasAnyRole,d1 as hasAnyPermission,p1 as hasAllRoles,n1 as hasAllPermissions,A1 as has,bW as givePermissionToRole,m1 as givePermission,Q0 as getUserRoles,z0 as getUserPermissions,aZ as getUserPasskeys,e$ as getUserPasskey,W$ as getTwoFactorState,i1 as getRolePermissions,R1 as getPolicyFor,s as getPasswordChangedAt,n2 as getCurrentUser,NW as getAllRoles,fW as getAllPermissions,J$ as getActiveTeamPreference,B1 as generateTwoFactorUri,kZ as generateTwoFactorToken,$Z as generateTwoFactorSetup,g0 as generateTwoFactorSecret,J4 as generateTOTPSecret,W4 as generateTOTP,J2 as generateRegistrationOptions,Q2 as generateAuthenticationOptions,N0 as flushRbacCache,T1 as flush,G1 as findToken,b as findRole,u as findPermission,b$ as findClient,QZ as enableTwoFactor,t2 as email,qW as discoverPolicies,GZ as disableTwoFactor,E1 as denies,AW as deleteRole,w$ as deleteRevokedTokens,A$ as deleteRevokedRefreshTokens,TW as deletePermission,f$ as deleteExpiredTokens,R$ as deleteExpiredRefreshTokens,C1 as define,n as currentAccessToken,YZ as createTwoFactorChallenge,f0 as createToken,r0 as createRole,lZ as createPersonalAccessClient,yW as createPermission,k$ as createClient,OW as createBqbRbacStore,$2 as consumeWebAuthnChallenge,KZ as consumeTwoFactorChallenge,JZ as consumePendingTwoFactorSecret,I$ as clients,y3 as clearActiveTeamCookie,HW as check,S0 as cannot,v0 as can,N3 as buildActiveTeamCookie,V2 as browserSupportsWebAuthnAutofill,L2 as browserSupportsWebAuthn,P1 as before,xW as authorizeUser,x0 as authorize,F0 as authUser,iZ as authMiddlewareHandler,s$ as authMiddleware,I1 as assignRole,_0 as any,S1 as allows,E0 as all,v1 as after,y1 as abilities,v3 as TwoFactor,U2 as SessionAuth,L3 as Rbac,k as RateLimiter,f1 as Gate,M3 as EmailVerification,t1 as DEFAULT_ROLE_PACKS,UW as BasePolicy,q as AuthorizationResponse,B0 as AuthorizationException,R as Auth,s0 as ACTIVE_TEAM_COOKIE};
@@ -1,2 +1,22 @@
1
1
  import type { Result } from '@stacksjs/error-handling';
2
- export declare function createPersonalAccessClient(): Promise<Result<string, never>>;
2
+ /**
3
+ * Create a personal access OAuth client.
4
+ *
5
+ * Idempotent: returns an `err({ code: 'already-exists' })` when a
6
+ * non-revoked personal access client already exists in the
7
+ * `oauth_clients` table, rather than inserting a duplicate. The
8
+ * previous behaviour silently created a second row, which made
9
+ * `getPersonalAccessClient()` (which `LIMIT 1`s with no `ORDER BY`)
10
+ * return whichever the DB happened to surface first — racy with
11
+ * subsequent token mints. See stacksjs/stacks#1860 M-7.
12
+ *
13
+ * The plaintext secret is what callers (e.g., `./buddy auth:token`)
14
+ * surface to the operator — they store it themselves. The DB holds
15
+ * only the bcrypt hash so a DB compromise doesn't leak usable client
16
+ * credentials (stacksjs/stacks#1861 M-1).
17
+ */
18
+ export declare function createPersonalAccessClient(): Promise<Result<string, CreatePersonalAccessClientError>>;
19
+ export declare interface CreatePersonalAccessClientError {
20
+ code: 'already-exists'
21
+ message: string
22
+ }
@@ -1,3 +1,4 @@
1
+ export type { SeedDefaultRolesResult } from './rbac-seed';
1
2
  export * from './authentication';
2
3
  export * from './authenticator';
3
4
  export * from './client';
@@ -16,6 +17,8 @@ export * from './policy';
16
17
  export * from './authorizable';
17
18
  // Role-Based Access Control (RBAC)
18
19
  export * from './rbac';
20
+ export { createBqbRbacStore } from './rbac-store-bqb';
21
+ export { DEFAULT_ROLE_PACKS, seedDefaultRoles } from './rbac-seed';
19
22
  // Email Verification
20
23
  export * from './email-verification';
21
24
  // Session-based Authentication (SPA Cookie Auth)
@@ -27,3 +30,7 @@ export {
27
30
  generateTOTPSecret,
28
31
  totpKeyUri,
29
32
  } from '@stacksjs/ts-auth';
33
+ // TOTP setup/enable/disable + login-challenge persistence
34
+ export * from './two-factor';
35
+ // Team resolution from auth credentials (dashboard-form scoping)
36
+ export * from './team';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Internal constants shared across the auth package.
3
+ *
4
+ * Lives in its own file so consumers like `authentication.ts` and
5
+ * `session-auth.ts` can pull the same literal without re-declaring it
6
+ * (stacksjs/stacks#1861 L-1).
7
+ */
8
+ /**
9
+ * Pre-computed bcrypt hash of nothing — used to keep timing constant
10
+ * when the lookup-by-email half of a login fails. Without it, the
11
+ * "user not found" branch would short-circuit fast and the "wrong
12
+ * password" branch would always pay the bcrypt cost, leaking
13
+ * "does this email exist?" via response time.
14
+ *
15
+ * The hash itself is intentionally non-verifiable: nothing the
16
+ * attacker types will ever match it, so the dummy compare always
17
+ * returns false but spends the same CPU as a real one would.
18
+ */
19
+ export declare const DUMMY_BCRYPT_HASH: '$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW';
@@ -14,7 +14,43 @@ export type {
14
14
  } from '@stacksjs/ts-auth';
15
15
  export declare function getUserPasskeys(userId: number): Promise<PasskeyAttribute[]>;
16
16
  export declare function getUserPasskey(userId: number, passkeyId: string): Promise<PasskeyAttribute | undefined>;
17
+ /**
18
+ * Persist the post-verification authenticator counter and refresh the
19
+ * passkey's last-used timestamp. WebAuthn's anti-cloning guarantee
20
+ * depends on the relying party rejecting any authentication whose
21
+ * `newCounter` is **not strictly greater** than the stored value —
22
+ * authenticators monotonically increment their counter on every use,
23
+ * so a counter that doesn't advance (or goes backwards) signals a
24
+ * cloned or replayed credential.
25
+ *
26
+ * Returns `true` when the counter was updated successfully; `false`
27
+ * when the new counter is not greater than the stored one (the
28
+ * authentication MUST be rejected by the caller in that case).
29
+ * stacksjs/stacks#1861 A-4.
30
+ */
31
+ export declare function updatePasskeyCounter(userId: number, passkeyId: string, newCounter: number): Promise<boolean>;
17
32
  export declare function setCurrentRegistrationOptions(user: UserModel, verified: VerifiedRegistrationResponse): Promise<void>;
33
+ /**
34
+ * Persist a server-issued WebAuthn challenge for the given user +
35
+ * purpose. Deletes any prior challenge for the same (user, purpose)
36
+ * pair so a fresh `generateOptions` invalidates the previous one.
37
+ *
38
+ * The unique index on `(user_id, purpose)` enforces single-outstanding
39
+ * at the DB layer; this delete makes the upsert safe even on installs
40
+ * that ran an earlier auth:setup before the unique index existed.
41
+ */
42
+ export declare function storeWebAuthnChallenge(userId: number, challenge: string, purpose: WebAuthnChallengePurpose, ttlSeconds?: number): Promise<void>;
43
+ /**
44
+ * Read + delete (single-use) a WebAuthn challenge for the given user
45
+ * + purpose. Returns `null` when no outstanding challenge exists or
46
+ * when the stored challenge has expired.
47
+ *
48
+ * Single-use semantics matter: a successful verify must invalidate
49
+ * the challenge so a captured assertion can't be replayed even within
50
+ * the TTL window. Callers MUST treat a `null` return as a verification
51
+ * failure.
52
+ */
53
+ export declare function consumeWebAuthnChallenge(userId: number, purpose: WebAuthnChallengePurpose): Promise<string | null>;
18
54
  export declare interface PasskeyAttribute {
19
55
  id: string
20
56
  cred_public_key: string
@@ -31,6 +67,21 @@ export declare interface PasskeyAttribute {
31
67
  }
32
68
  declare type UserModel = InstanceType<typeof User>;
33
69
  declare type PasskeyInsertable = Insertable<PasskeyAttribute>;
70
+ // =============================================================================
71
+ // WebAuthn challenge persistence (stacksjs/stacks#1866)
72
+ // =============================================================================
73
+ //
74
+ // WebAuthn relying parties MUST verify the assertion's challenge
75
+ // against a server-issued nonce. Previously Stacks returned the
76
+ // challenge in `generateOptions` and trusted the client to echo it
77
+ // back on verify — so any attacker who captured the assertion AND the
78
+ // challenge could replay the response. Persisting the challenge
79
+ // server-side and consuming it on verify closes that gap.
80
+ //
81
+ // The default TTL is 5 minutes — long enough for slow biometric
82
+ // flows, short enough to bound the replay window. Override per-call
83
+ // via the `ttlSeconds` parameter.
84
+ export type WebAuthnChallengePurpose = 'registration' | 'authentication';
34
85
  // Re-export WebAuthn functions from ts-auth
35
86
  export {
36
87
  generateRegistrationOptions,
@@ -1,6 +1,44 @@
1
+ export declare interface RateLimitEntry {
2
+ attempts: number
3
+ lockedUntil: number
4
+ }
5
+ /**
6
+ * Pluggable backing store for the auth rate limiter.
7
+ *
8
+ * Methods may be sync or async — the limiter awaits them either way. The
9
+ * default {@link MemoryStore} is process-local (fine for single-instance and
10
+ * dev). On a horizontally-scaled deployment the in-memory store is trivially
11
+ * bypassed by spreading attempts across instances, so production should swap
12
+ * in a shared store via `RateLimiter.useSharedStore()` (cache-backed; becomes
13
+ * cluster-wide when the cache driver is Redis) or a custom `useStore()`.
14
+ */
15
+ export declare interface RateLimiterStore {
16
+ get: (key: string) => Promise<RateLimitEntry | undefined> | RateLimitEntry | undefined
17
+ set: (key: string, entry: RateLimitEntry, ttlMs: number) => Promise<void> | void
18
+ delete: (key: string) => Promise<void> | void
19
+ }
20
+ /** Process-local store — the default. */
21
+ declare class MemoryStore implements RateLimiterStore {
22
+ get(key: string): RateLimitEntry | undefined;
23
+ set(key: string, entry: RateLimitEntry): void;
24
+ delete(key: string): void;
25
+ }
26
+ /**
27
+ * Cache-backed store. Cross-instance when the configured cache driver is
28
+ * Redis; otherwise behaves like an in-memory store with TTL eviction. Entries
29
+ * carry a TTL so attempts decay automatically — no separate eviction pass.
30
+ */
31
+ declare class CacheStore implements RateLimiterStore {
32
+ get(key: string): Promise<RateLimitEntry | undefined>;
33
+ set(key: string, entry: RateLimitEntry, ttlMs: number): Promise<void>;
34
+ delete(key: string): Promise<void>;
35
+ }
1
36
  export declare class RateLimiter {
2
- static isRateLimited(email: string): boolean;
3
- static recordFailedAttempt(email: string): void;
4
- static resetAttempts(email: string): void;
5
- static validateAttempt(email: string): void;
37
+ static useStore(custom: RateLimiterStore): void;
38
+ static useSharedStore(): void;
39
+ static useMemoryStore(): void;
40
+ static isRateLimited(email: string): Promise<boolean>;
41
+ static recordFailedAttempt(email: string): Promise<void>;
42
+ static resetAttempts(email: string): Promise<void>;
43
+ static validateAttempt(email: string): Promise<void>;
6
44
  }
@@ -0,0 +1,35 @@
1
+ import type { RoleRecord } from './rbac';
2
+ /**
3
+ * Idempotently seed the three default role packs. Existing role records
4
+ * with the same `(name, guard_name)` are left untouched.
5
+ *
6
+ * Throws only if the underlying `createRole` throws for a reason other
7
+ * than a unique-constraint race (the BqbRbacStore's `swallowDuplicate`
8
+ * already handles that case). Caller should typically log the error and
9
+ * surface it — a missing roles table means the migrations haven't run
10
+ * yet, which is a different bug than a seeder failure.
11
+ */
12
+ export declare function seedDefaultRoles(): Promise<SeedDefaultRolesResult>;
13
+ /**
14
+ * The role packs every Stacks install starts with. `useRole()`'s built-in
15
+ * predicates (`isAdmin`, `isDev`, `isClient`) check these names verbatim,
16
+ * so renaming them in a project effectively unbinds the composable from
17
+ * its defaults — that's fine, but obvious to readers.
18
+ */
19
+ export declare const DEFAULT_ROLE_PACKS: readonly [{
20
+ name: 'admin';
21
+ guard_name: 'web';
22
+ description: 'Full access. Sees every dashboard surface, every model, every infra control.'
23
+ }, {
24
+ name: 'dev';
25
+ guard_name: 'web';
26
+ description: 'Developer / infra. Sees dev-mode surfaces (CI, query inspector, runner alerts) but not billing/admin-only management.'
27
+ }, {
28
+ name: 'client';
29
+ guard_name: 'web';
30
+ description: 'End user / client. Sees content, orders, profile, billing — no dev tools, no infra surfaces.'
31
+ }];
32
+ export declare interface SeedDefaultRolesResult {
33
+ created: RoleRecord[]
34
+ skipped: Array<{ name: string, guard_name: string, reason: 'already_exists' }>
35
+ }
@@ -0,0 +1,18 @@
1
+ import { isUniqueViolation } from '@stacksjs/orm';
2
+ import type { PermissionRecord, RbacStore, RoleRecord } from './rbac';
3
+ /**
4
+ * Swallow unique-constraint violations. Anything else is re-thrown so a
5
+ * "connection lost" mid-INSERT doesn't get silently treated as
6
+ * "already assigned".
7
+ *
8
+ * Exported for direct unit testing — the live pivot helpers below also use it.
9
+ */
10
+ export declare function swallowDuplicate(err: unknown): void;
11
+ /**
12
+ * Map a raw DB row to a typed record. Both `roles` and `permissions` share
13
+ * the same shape, so one mapper covers both — the call site provides the
14
+ * generic. Exported for unit testing.
15
+ */
16
+ export declare function toRecord<T extends RoleRecord | PermissionRecord>(row: Record<string, unknown> | undefined): T | null;
17
+ export declare function createBqbRbacStore(): RbacStore;
18
+ export { isUniqueViolation };
@@ -216,4 +216,21 @@ export declare interface RbacMethods {
216
216
  // Use the row/instance shape from orm so role helpers operate on the
217
217
  // authenticated user object, not the User class constructor.
218
218
  declare type UserModel = OrmUserModel;
219
+ /**
220
+ * FIFO-bounded Map. Wraps Map with a hard size cap; on overflow, the
221
+ * oldest entry (Map insertion order) is evicted. Used for the RBAC
222
+ * per-user caches because the previous unbounded `Map<number, ...>`
223
+ * grew without limit across the process lifetime — a long-running
224
+ * server serving many distinct users would OOM eventually
225
+ * (stacksjs/stacks#1860 M-5). Same shape as `BoundedMap` in
226
+ * `@stacksjs/router/stacks-router.ts`.
227
+ */
228
+ declare class BoundedMap<K, V> {
229
+ constructor(max: number);
230
+ get(key: K): V | undefined;
231
+ has(key: K): boolean;
232
+ set(key: K, value: V): this;
233
+ delete(key: K): boolean;
234
+ clear(): void;
235
+ }
219
236
  export default Rbac;
@@ -2,12 +2,30 @@ import { User } from '@stacksjs/orm';
2
2
  /**
3
3
  * Authenticate a user via email and password, creating a session.
4
4
  * Sessions are persisted to the database so they survive server restarts.
5
+ *
6
+ * The `fingerprint` override is mainly for tests; in normal HTTP
7
+ * handling the active request's IP + UA are captured automatically.
5
8
  */
6
- export declare function sessionLogin(email: string, password: string): Promise<{ user: UserModel, sessionId: string }>;
9
+ export declare function sessionLogin(email: string, password: string, fingerprint?: { ip?: string | null, userAgent?: string | null }): Promise<{ user: UserModel, sessionId: string }>;
7
10
  /**
8
11
  * Destroy the session for the given session ID.
9
12
  */
10
13
  export declare function sessionLogout(sessionId: string): Promise<void>;
14
+ /**
15
+ * Destroy every session for a user — the credential-change sweep.
16
+ * Sessions are validated purely on row existence + `expires_at`, never
17
+ * re-checked against the password hash, so without this a stolen
18
+ * session cookie survives a password reset for up to 24h
19
+ * (stacksjs/stacks#1947).
20
+ *
21
+ * Unlike `sessionLogout`, real failures propagate (fail loud): a reset
22
+ * that reports success while the attacker's session lives would be a
23
+ * lie. A missing `sessions` table alone is a benign no-op — no
24
+ * framework migration creates it (only userland adopting session-auth
25
+ * does), and without the table `sessionCheck` can never validate a
26
+ * session, so there is no credential left to revoke.
27
+ */
28
+ export declare function sessionDestroyAll(userId: number): Promise<void>;
11
29
  /**
12
30
  * Get the authenticated user from a session ID.
13
31
  */
@@ -23,6 +41,7 @@ export declare function sessionRefresh(sessionId: string, ttlMs?: unknown): Prom
23
41
  export declare const SessionAuth: {
24
42
  login: unknown;
25
43
  logout: unknown;
44
+ destroyAll: unknown;
26
45
  user: unknown;
27
46
  check: unknown;
28
47
  refresh: unknown
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Pure resolver — no I/O — that decides which team is "active" for a user
3
+ * given their memberships and an optional switch preference. Reusable across
4
+ * apps: it encodes the whole workspace-switching precedence in one place.
5
+ *
6
+ * - Honors `activeTeamId` only when the user may access it: a member always
7
+ * may; a privileged operator may access any team when `allowAnyTeam` is set
8
+ * (the app decides who is privileged — e.g. a super-admin flag).
9
+ * - Otherwise falls back to the highest-priority membership (owner > admin >
10
+ * anything else), matching what dashboard pages have always shown, so the
11
+ * "current team" never differs between a rendered form and the action it
12
+ * posts to.
13
+ */
14
+ export declare function selectActiveTeam(opts: {
15
+ memberships: Array<{ team_id: number | string, role?: string | null }>
16
+ activeTeamId?: number | null
17
+ allowAnyTeam?: boolean
18
+ rolePriority?: Record<string, number>
19
+ }): { teamId: number | null, role: string | null };
20
+ /** Read the active-team switch preference from a request's cookie. */
21
+ export declare function getActiveTeamPreference(request: TeamAuthRequest): number | null;
22
+ /**
23
+ * Build the `Set-Cookie` that pins the active team (workspace switcher). A
24
+ * year-long HttpOnly cookie: only the server needs it (SSR reads it, the
25
+ * switch action writes it), and it carries no privilege of its own.
26
+ */
27
+ export declare function buildActiveTeamCookie(teamId: number, opts?: { maxAgeSeconds?: number, secure?: boolean }): string;
28
+ /** Clear the active-team cookie {@link buildActiveTeamCookie} sets. */
29
+ export declare function clearActiveTeamCookie(): string;
30
+ /**
31
+ * Resolve the requesting user's active team membership (team id + role)
32
+ * from their auth credential. Driver-aware: reads config/auth.ts's
33
+ * configured guard driver rather than hardcoding a validation scheme.
34
+ *
35
+ * Dashboard forms are plain HTML POSTs (no client JS, no Authorization
36
+ * header) — the browser only ever sends the auth cookie set on login,
37
+ * so that's checked for the 'token' driver alongside a bearer-header
38
+ * fallback for JS/API callers.
39
+ *
40
+ * Owner membership wins over admin, which wins over any other active
41
+ * membership, when a user belongs to more than one team — the same
42
+ * precedence server-rendered dashboard pages use, so a user never sees
43
+ * a different "current team" between the page that rendered a form and
44
+ * the action that form posts to.
45
+ *
46
+ * Returns `null` when unauthenticated or without an active team
47
+ * membership — callers must treat that as "reject the request," not
48
+ * "fall back to a default team."
49
+ */
50
+ export declare function resolveAuthenticatedMembership(request: TeamAuthRequest): Promise<TeamMembershipResult | null>;
51
+ /**
52
+ * Full team context for a server-rendered dashboard: the authenticated user,
53
+ * the resolved active team (honoring the workspace switcher), and the list of
54
+ * teams the user can switch between. One call replaces the per-page auth+team
55
+ * boilerplate every dashboard view used to copy, and centralizes the
56
+ * switch-aware scoping so pages and the actions they post to never disagree.
57
+ *
58
+ * `opts.allowAnyTeam(user)` lets an app grant an operator (e.g. a super-admin)
59
+ * access to every team — they can switch to, and see, teams they aren't a
60
+ * member of. The full user row is returned so the app can read its own
61
+ * columns (like a super-admin flag) without a second query.
62
+ */
63
+ export declare function resolveTeamContext(request: TeamAuthRequest, opts?: { allowAnyTeam?: (user: any) => boolean }): Promise<TeamContext>;
64
+ /**
65
+ * Team id only — the common case for actions that just need to scope a
66
+ * write to the requester's team. See {@link resolveAuthenticatedMembership}
67
+ * when the caller also needs the role (e.g. owner/admin-only settings).
68
+ */
69
+ export declare function resolveAuthenticatedTeamId(request: TeamAuthRequest): Promise<number | null>;
70
+ /**
71
+ * The authenticated USER (not team) from a request's real auth
72
+ * credential — bearer header first, then the login cookie, driver-aware
73
+ * like {@link resolveAuthenticatedMembership} (which builds on this).
74
+ * For dashboard form actions that operate on the requester themselves
75
+ * (security settings, profile) rather than on team-scoped rows: plain
76
+ * HTML POSTs carry no Authorization header, so `request.user()` (stamped
77
+ * by the auth middleware from a bearer/session) is undefined there and
78
+ * the login cookie is the only credential available.
79
+ */
80
+ export declare function resolveAuthenticatedUser(request: TeamAuthRequest): Promise<{ id: number, email?: string } | undefined>;
81
+ /**
82
+ * Name of the cookie that remembers which team the user last switched the
83
+ * dashboard to (a workspace switcher). It is NOT a security credential: the
84
+ * server re-validates membership against `selectActiveTeam` on every request,
85
+ * so a tampered value can only ever resolve to a team the user already
86
+ * belongs to (or, for operators, any team when `allowAnyTeam` is set).
87
+ */
88
+ export declare const ACTIVE_TEAM_COOKIE: 'active_team';
89
+ /**
90
+ * Team resolution from a request's real auth credential (bearer token or
91
+ * session cookie) — never from a client-supplied form field. Extracted
92
+ * from app-land (stacksjs/status config/auth-team.ts) because every app
93
+ * with team-scoped dashboard forms needs exactly this, and `config/` is
94
+ * for autoloaded config files, not shared helpers.
95
+ *
96
+ * Structural request type on purpose: callers pass whatever request
97
+ * object their action received. Partial objects (tests, non-HTTP
98
+ * callers) resolve to "unauthenticated" rather than crashing.
99
+ */
100
+ export declare interface TeamAuthRequest {
101
+ bearerToken?: () => string | null | undefined
102
+ cookies?: { get: (name: string) => string | null | undefined }
103
+ }
104
+ export declare interface TeamMembershipResult {
105
+ teamId: number
106
+ role: string
107
+ }
108
+ /** A team the authenticated user may switch the dashboard to. */
109
+ export declare interface SwitchableTeam {
110
+ id: number
111
+ name: string
112
+ role: string
113
+ }
114
+ /** Full dashboard team context — see {@link resolveTeamContext}. */
115
+ export declare interface TeamContext {
116
+ user: any | null
117
+ teamId: number | null
118
+ role: string | null
119
+ teams: SwitchableTeam[]
120
+ activeTeamId: number | null
121
+ }
@@ -1,4 +1,33 @@
1
1
  import type { AccessToken, CreateClientOptions, CreateClientResult, OAuthClient, PersonalAccessTokenResult, RefreshTokenResult, TokenScopes } from '@stacksjs/types';
2
+ /**
3
+ * Read `users.password_changed_at` for a user.
4
+ *
5
+ * Binds a token's validity to the account's credential state: a token
6
+ * issued before the user last changed their password is no longer
7
+ * trusted, regardless of its own `revoked`/`expires_at` flags. This is
8
+ * the durable, use-time backstop behind the post-reset revocation sweep
9
+ * (#1947) — even a freshly minted pair that the sweep never saw is
10
+ * rejected on first use.
11
+ *
12
+ * Returns `null` on ANY error (missing column / missing table) so a
13
+ * not-yet-migrated database degrades to legacy-allow rather than locking
14
+ * everyone out. Accepts an optional query runner so the refresh exchange
15
+ * can read the stamp inside its own transaction.
16
+ */
17
+ export declare function getPasswordChangedAt(userId: unknown, q?: { unsafe: (sql: string, params?: any[]) => any }): Promise<Date | null>;
18
+ /**
19
+ * True when a credential issued at `createdAt` predates the user's last
20
+ * password change (`changedAt`) and must therefore be rejected.
21
+ *
22
+ * Legacy-allow semantics:
23
+ * - `changedAt` null (no stamp / un-migrated) => never reject.
24
+ * - `createdAt` missing/unparseable => never reject.
25
+ *
26
+ * Strict `<` so a token minted in the SAME second as (or after) the
27
+ * reset — e.g. the victim's immediate post-reset login — is NOT bricked
28
+ * by CURRENT_TIMESTAMP's one-second granularity.
29
+ */
30
+ export declare function isIssuedBeforePasswordChange(createdAt: unknown, changedAt: Date | null): boolean;
2
31
  /**
3
32
  * Get all access tokens for a user
4
33
  *
@@ -241,6 +270,8 @@ export declare function revokeClient(clientId: number): Promise<void>;
241
270
  // HELPER FUNCTIONS
242
271
  // ============================================================================
243
272
  export declare function parseScopes(scopes: string | string[] | null | undefined): TokenScopes;
273
+ /** Cross-database SQL helpers */
274
+ declare const sql: unknown;
244
275
  /**
245
276
  * Alias for currentAccessToken
246
277
  *
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Reads `two_factor_secret`/`two_factor_enabled` directly — these are
3
+ * guarantee-ALTER columns (see ensureUsersAuthColumns), not part of
4
+ * the User model's typed `attributes`, so callers query them the same
5
+ * way email-verification.ts reads email_verified_at: raw db access,
6
+ * not the ORM model.
7
+ */
8
+ export declare function getTwoFactorState(userId: number): Promise<{ secret: string | null, enabled: boolean }>;
9
+ export declare function isTwoFactorEnabled(user: TwoFactorUser): boolean;
10
+ /**
11
+ * Generate a new (unpersisted) secret + otpauth:// URI for setup.
12
+ */
13
+ export declare function generateTwoFactorSetup(email: string, serviceName?: string): { secret: string, uri: string };
14
+ /**
15
+ * Stash a freshly generated secret server-side while the user goes
16
+ * scan/enter it into their authenticator app. Single pending secret
17
+ * per user — generating a new one invalidates any prior unconfirmed
18
+ * attempt, same delete-then-insert shape as storeWebAuthnChallenge.
19
+ */
20
+ export declare function stashPendingTwoFactorSecret(userId: number, secret: string, ttlSeconds?: number): Promise<void>;
21
+ /**
22
+ * Consume (delete-on-read) the pending secret stashed for a user, or
23
+ * null if none exists / it expired.
24
+ */
25
+ export declare function consumePendingTwoFactorSecret(userId: number): Promise<string | null>;
26
+ /**
27
+ * Verify the setup code against the not-yet-persisted secret and, if
28
+ * valid, persist it + flip `two_factor_enabled` on.
29
+ */
30
+ export declare function enableTwoFactor(userId: number, secret: string, code: string): Promise<boolean>;
31
+ export declare function disableTwoFactor(userId: number): Promise<void>;
32
+ /**
33
+ * Verify a live login/dashboard-reauth code against the user's
34
+ * already-persisted secret.
35
+ */
36
+ export declare function verifyTwoFactorLoginCode(userId: number, code: string): Promise<boolean>;
37
+ /**
38
+ * Create a single-use, short-lived login challenge for a user whose
39
+ * password just verified but who still needs to supply a TOTP code.
40
+ * Mirrors storeWebAuthnChallenge's delete-then-insert shape, keyed by
41
+ * an opaque random id instead of (user_id, purpose) since a user can
42
+ * only have one login attempt in flight that matters here.
43
+ */
44
+ export declare function createTwoFactorChallenge(userId: number, ttlSeconds?: number): Promise<string>;
45
+ /**
46
+ * Consume (delete-on-read) a login challenge and return the user id it
47
+ * was issued for, or null if missing/expired.
48
+ */
49
+ export declare function consumeTwoFactorChallenge(challengeToken: string): Promise<number | null>;
50
+ export declare const TwoFactor: {
51
+ isEnabled: unknown;
52
+ getState: unknown;
53
+ generateSetup: unknown;
54
+ stashPendingSecret: unknown;
55
+ consumePendingSecret: unknown;
56
+ enable: unknown;
57
+ disable: unknown;
58
+ verifyLoginCode: unknown;
59
+ createChallenge: unknown;
60
+ consumeChallenge: unknown
61
+ };
62
+ export declare interface TwoFactorUser {
63
+ id: number
64
+ email?: string
65
+ two_factor_secret?: string | null
66
+ two_factor_enabled?: boolean | number | null
67
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/auth",
3
3
  "type": "module",
4
- "version": "0.70.45",
4
+ "version": "0.70.54",
5
5
  "description": "A more simplistic way to authenticate.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -46,11 +46,11 @@
46
46
  "prepublishOnly": "bun run build"
47
47
  },
48
48
  "dependencies": {
49
- "@stacksjs/ts-auth": "^0.4.1"
49
+ "@stacksjs/ts-auth": "^0.4.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "better-dx": "^0.2.12",
53
- "@stacksjs/error-handling": "^0.70.45",
54
- "@stacksjs/router": "^0.70.45"
53
+ "@stacksjs/error-handling": "0.70.54",
54
+ "@stacksjs/router": "0.70.54"
55
55
  }
56
56
  }