@stacksjs/auth 0.70.87 → 0.70.90
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/dist/authentication.js +413 -0
- package/dist/authenticator.js +14 -0
- package/dist/authorizable.js +28 -0
- package/dist/client.js +26 -0
- package/dist/email-verification.js +111 -0
- package/dist/gate.js +203 -0
- package/dist/index.js +26 -165
- package/dist/internal-constants.js +1 -0
- package/dist/middleware.js +28 -0
- package/dist/passkey.js +76 -0
- package/dist/password/reset.js +156 -0
- package/dist/policy.js +91 -0
- package/dist/rate-limiter.js +91 -0
- package/dist/rbac-seed.js +30 -0
- package/dist/rbac-store-bqb.js +180 -0
- package/dist/rbac.js +324 -0
- package/dist/register.js +43 -0
- package/dist/session-auth.d.ts +18 -0
- package/dist/session-auth.js +151 -0
- package/dist/team.js +88 -0
- package/dist/token.js +21 -0
- package/dist/tokens.js +489 -0
- package/dist/two-factor.js +113 -0
- package/dist/user.js +41 -0
- package/package.json +3 -3
package/dist/gate.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const RESERVED_POLICY_MEMBERS = new Set([
|
|
2
|
+
"before",
|
|
3
|
+
"allow",
|
|
4
|
+
"deny",
|
|
5
|
+
"denyIf",
|
|
6
|
+
"denyUnless",
|
|
7
|
+
"allowIf",
|
|
8
|
+
"constructor"
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export class AuthorizationResponse {
|
|
12
|
+
isAllowed;
|
|
13
|
+
message;
|
|
14
|
+
code;
|
|
15
|
+
constructor(allowed, message, code) {
|
|
16
|
+
this.isAllowed = allowed;
|
|
17
|
+
this.message = message;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
static allow(message) {
|
|
21
|
+
return new AuthorizationResponse(!0, message);
|
|
22
|
+
}
|
|
23
|
+
static deny(message, code) {
|
|
24
|
+
return new AuthorizationResponse(!1, message || "This action is unauthorized.", code);
|
|
25
|
+
}
|
|
26
|
+
allowed() {
|
|
27
|
+
return this.isAllowed;
|
|
28
|
+
}
|
|
29
|
+
denied() {
|
|
30
|
+
return !this.isAllowed;
|
|
31
|
+
}
|
|
32
|
+
authorize() {
|
|
33
|
+
if (!this.isAllowed)
|
|
34
|
+
throw new AuthorizationException(this.message || "This action is unauthorized.", this.code);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class AuthorizationException extends Error {
|
|
39
|
+
code;
|
|
40
|
+
status;
|
|
41
|
+
constructor(message = "This action is unauthorized.", code, status = 403) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.status = status;
|
|
45
|
+
this.name = "AuthorizationException";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const state = {
|
|
49
|
+
gates: new Map,
|
|
50
|
+
policies: new Map,
|
|
51
|
+
beforeCallbacks: [],
|
|
52
|
+
afterCallbacks: []
|
|
53
|
+
};
|
|
54
|
+
export function define(ability, callback) {
|
|
55
|
+
state.gates.set(ability, callback);
|
|
56
|
+
}
|
|
57
|
+
export function policy(model, policyClass) {
|
|
58
|
+
const modelName = typeof model === "string" ? model : model.name;
|
|
59
|
+
state.policies.set(modelName, policyClass);
|
|
60
|
+
}
|
|
61
|
+
export function before(callback) {
|
|
62
|
+
state.beforeCallbacks.push(callback);
|
|
63
|
+
}
|
|
64
|
+
export function after(callback) {
|
|
65
|
+
state.afterCallbacks.push(callback);
|
|
66
|
+
}
|
|
67
|
+
export async function allows(ability, user, ...args) {
|
|
68
|
+
return check(ability, user, ...args);
|
|
69
|
+
}
|
|
70
|
+
export async function denies(ability, user, ...args) {
|
|
71
|
+
return !await check(ability, user, ...args);
|
|
72
|
+
}
|
|
73
|
+
export async function can(ability, user, ...args) {
|
|
74
|
+
return check(ability, user, ...args);
|
|
75
|
+
}
|
|
76
|
+
export async function cannot(ability, user, ...args) {
|
|
77
|
+
return !await check(ability, user, ...args);
|
|
78
|
+
}
|
|
79
|
+
export async function any(abilities, user, ...args) {
|
|
80
|
+
for (const ability of abilities)
|
|
81
|
+
if (await check(ability, user, ...args))
|
|
82
|
+
return !0;
|
|
83
|
+
return !1;
|
|
84
|
+
}
|
|
85
|
+
export async function all(abilities, user, ...args) {
|
|
86
|
+
for (const ability of abilities)
|
|
87
|
+
if (!await check(ability, user, ...args))
|
|
88
|
+
return !1;
|
|
89
|
+
return !0;
|
|
90
|
+
}
|
|
91
|
+
export async function none(abilities, user, ...args) {
|
|
92
|
+
return !await any(abilities, user, ...args);
|
|
93
|
+
}
|
|
94
|
+
export async function authorize(ability, user, ...args) {
|
|
95
|
+
const result = await inspect(ability, user, ...args);
|
|
96
|
+
if (!result.isAllowed)
|
|
97
|
+
throw new AuthorizationException(result.message, result.code);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
export async function inspect(ability, user, ...args) {
|
|
101
|
+
let response = null;
|
|
102
|
+
for (const callback of state.beforeCallbacks) {
|
|
103
|
+
const beforeResult = await callback(user, ability, args);
|
|
104
|
+
if (beforeResult === !0) {
|
|
105
|
+
response = AuthorizationResponse.allow();
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (beforeResult === !1) {
|
|
109
|
+
response = AuthorizationResponse.deny();
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!response)
|
|
114
|
+
response = await resolveAbility(ability, user, args);
|
|
115
|
+
for (const callback of state.afterCallbacks) {
|
|
116
|
+
const afterResult = await callback(user, ability, response.isAllowed, args);
|
|
117
|
+
if (typeof afterResult === "boolean")
|
|
118
|
+
return afterResult ? AuthorizationResponse.allow() : AuthorizationResponse.deny();
|
|
119
|
+
}
|
|
120
|
+
return response;
|
|
121
|
+
}
|
|
122
|
+
async function resolveAbility(ability, user, args) {
|
|
123
|
+
const model = args[0];
|
|
124
|
+
if (model && typeof model === "object") {
|
|
125
|
+
const modelName = model.constructor?.name, policyClass = state.policies.get(modelName);
|
|
126
|
+
if (policyClass) {
|
|
127
|
+
const policyInstance = new policyClass;
|
|
128
|
+
if (policyInstance.before) {
|
|
129
|
+
const beforeResult = await policyInstance.before(user, ability);
|
|
130
|
+
if (beforeResult === !0)
|
|
131
|
+
return AuthorizationResponse.allow();
|
|
132
|
+
if (beforeResult === !1)
|
|
133
|
+
return AuthorizationResponse.deny();
|
|
134
|
+
}
|
|
135
|
+
const method = RESERVED_POLICY_MEMBERS.has(ability) ? void 0 : policyInstance[ability];
|
|
136
|
+
if (typeof method === "function") {
|
|
137
|
+
const result = await method.call(policyInstance, user, ...args);
|
|
138
|
+
return normalizeResponse(result ?? !1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const gate = state.gates.get(ability);
|
|
143
|
+
if (gate)
|
|
144
|
+
return normalizeResponse(await gate(user, ...args));
|
|
145
|
+
return AuthorizationResponse.deny(`No gate or policy defined for ability: ${ability}`);
|
|
146
|
+
}
|
|
147
|
+
async function check(ability, user, ...args) {
|
|
148
|
+
return (await inspect(ability, user, ...args)).isAllowed;
|
|
149
|
+
}
|
|
150
|
+
function normalizeResponse(result) {
|
|
151
|
+
if (result instanceof AuthorizationResponse)
|
|
152
|
+
return result;
|
|
153
|
+
if (typeof result !== "boolean")
|
|
154
|
+
throw TypeError(`[gate] Policy must return boolean or AuthorizationResponse; got ${typeof result}. If you returned a model/value by mistake, return \`true\`/\`false\` instead.`);
|
|
155
|
+
return result ? AuthorizationResponse.allow() : AuthorizationResponse.deny();
|
|
156
|
+
}
|
|
157
|
+
export function getPolicyFor(model) {
|
|
158
|
+
if (!model || typeof model !== "object")
|
|
159
|
+
return null;
|
|
160
|
+
const modelName = model.constructor?.name, policyClass = state.policies.get(modelName);
|
|
161
|
+
if (policyClass)
|
|
162
|
+
return new policyClass;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
export function has(ability) {
|
|
166
|
+
return state.gates.has(ability);
|
|
167
|
+
}
|
|
168
|
+
export function hasPolicy(model) {
|
|
169
|
+
const modelName = typeof model === "string" ? model : model.name;
|
|
170
|
+
return state.policies.has(modelName);
|
|
171
|
+
}
|
|
172
|
+
export function abilities() {
|
|
173
|
+
return Array.from(state.gates.keys());
|
|
174
|
+
}
|
|
175
|
+
export function flush() {
|
|
176
|
+
state.gates.clear();
|
|
177
|
+
state.policies.clear();
|
|
178
|
+
state.beforeCallbacks = [];
|
|
179
|
+
state.afterCallbacks = [];
|
|
180
|
+
}
|
|
181
|
+
export const Gate = {
|
|
182
|
+
define,
|
|
183
|
+
policy,
|
|
184
|
+
before,
|
|
185
|
+
after,
|
|
186
|
+
allows,
|
|
187
|
+
denies,
|
|
188
|
+
can,
|
|
189
|
+
cannot,
|
|
190
|
+
any,
|
|
191
|
+
all,
|
|
192
|
+
none,
|
|
193
|
+
authorize,
|
|
194
|
+
inspect,
|
|
195
|
+
has,
|
|
196
|
+
hasPolicy,
|
|
197
|
+
abilities,
|
|
198
|
+
getPolicyFor,
|
|
199
|
+
flush,
|
|
200
|
+
AuthorizationResponse,
|
|
201
|
+
AuthorizationException
|
|
202
|
+
};
|
|
203
|
+
export default Gate;
|
package/dist/index.js
CHANGED
|
@@ -1,165 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`,[O]))[0];if(!F)throw new Q0(500,"Failed to create access token \u2014 inserted row not found");let j={id:F.id,userId:F.user_id,clientId:F.oauth_client_id,name:F.name,scopes:w(F.scopes),revoked:!1,expiresAt:_,createdAt:new Date(F.created_at),updatedAt:new Date(F.updated_at)},C;if(G){C=G0(40);let N=b(C),R=new Date;if(R.setDate(R.getDate()+X),o)await B.unsafe(`
|
|
28
|
-
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
29
|
-
VALUES ($1, $2, false, $3, NOW())
|
|
30
|
-
`,[j.id,N,R.toISOString()]);else await B.unsafe(`
|
|
31
|
-
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
32
|
-
VALUES (?, ?, 0, ?, ${M})
|
|
33
|
-
`,[j.id,N,R.toISOString()])}return{accessToken:j,plainTextToken:L,refreshToken:C,expiresIn:Q*60}}async function D$($,W={}){let{expiresInMinutes:Z=60,refreshExpiresInDays:J=30}=W,Q=b($);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 = ${X0}
|
|
39
|
-
AND (r.expires_at IS NULL OR r.expires_at > ${M})
|
|
40
|
-
LIMIT 1
|
|
41
|
-
`,[Q]))[0];if(!Y)throw new Q0(401,"Invalid or expired refresh token");if(u(Y.created_at,await a(Y.user_id,X)))throw new Q0(401,"Invalid or expired refresh token");await X.unsafe(`
|
|
42
|
-
UPDATE oauth_refresh_tokens
|
|
43
|
-
SET revoked = ${P}
|
|
44
|
-
WHERE id = ${z(1)}
|
|
45
|
-
`,[Y.id]),await X.unsafe(`
|
|
46
|
-
UPDATE oauth_access_tokens
|
|
47
|
-
SET revoked = ${P}
|
|
48
|
-
WHERE id = ${z(1)}
|
|
49
|
-
`,[Y.access_token_id]);let L=G0(40),O=b(L),_=new Date;if(_.setMinutes(_.getMinutes()+Z),o)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, ?, ${M}, ${M})
|
|
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],j={id:F.id,userId:F.user_id,clientId:F.oauth_client_id,name:F.name,scopes:w(F.scopes),revoked:!1,expiresAt:_,createdAt:new Date(F.created_at),updatedAt:new Date(F.updated_at)};if(u(F.created_at,await a(Y.user_id,X)))throw new Q0(401,"Invalid or expired refresh token");let C=G0(40),N=b(C),R=new Date;if(R.setDate(R.getDate()+J),o)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
|
-
`,[j.id,N,R.toISOString()]);else await X.unsafe(`
|
|
61
|
-
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
62
|
-
VALUES (?, ?, 0, ?, ${M})
|
|
63
|
-
`,[j.id,N,R.toISOString()]);return{accessToken:j,plainTextToken:L,refreshToken:C,expiresIn:Z*60}})}async function j$($){let W=b($);return(await B.unsafe(`
|
|
64
|
-
SELECT id FROM oauth_refresh_tokens
|
|
65
|
-
WHERE token = ${z(1)}
|
|
66
|
-
AND revoked = ${X0}
|
|
67
|
-
AND (expires_at IS NULL OR expires_at > ${M})
|
|
68
|
-
LIMIT 1
|
|
69
|
-
`,[W])).length>0}async function H$($){let W=b($);await B.unsafe(`
|
|
70
|
-
UPDATE oauth_refresh_tokens
|
|
71
|
-
SET revoked = ${P}
|
|
72
|
-
WHERE token = ${z(1)}
|
|
73
|
-
`,[W])}async function e0($){await B.unsafe(`
|
|
74
|
-
UPDATE oauth_refresh_tokens
|
|
75
|
-
SET revoked = ${P}
|
|
76
|
-
WHERE access_token_id IN (
|
|
77
|
-
SELECT id FROM oauth_access_tokens WHERE user_id = ${z(1)}
|
|
78
|
-
)
|
|
79
|
-
`,[$])}async function M$(){let $=await B.unsafe(`
|
|
80
|
-
DELETE FROM oauth_refresh_tokens
|
|
81
|
-
WHERE expires_at < ${M}
|
|
82
|
-
`);return $?.changes||$?.rowCount||0}async function U$($=7){let W=new Date;W.setDate(W.getDate()-$);let Z=await B.unsafe(`
|
|
83
|
-
DELETE FROM oauth_refresh_tokens
|
|
84
|
-
WHERE revoked = ${P} AND created_at < ${z(1)}
|
|
85
|
-
`,[W.toISOString()]);return Z?.changes||Z?.rowCount||0}async function q$($){let W=a0($);await B.unsafe(`
|
|
86
|
-
UPDATE oauth_refresh_tokens
|
|
87
|
-
SET revoked = ${P}
|
|
88
|
-
WHERE access_token_id IN (
|
|
89
|
-
SELECT id FROM oauth_access_tokens WHERE token = ${z(1)}
|
|
90
|
-
)
|
|
91
|
-
`,[W]),await B.unsafe(`
|
|
92
|
-
UPDATE oauth_access_tokens
|
|
93
|
-
SET revoked = ${P}, updated_at = ${M}
|
|
94
|
-
WHERE token = ${z(1)}
|
|
95
|
-
`,[W])}async function C$($){await B.unsafe(`
|
|
96
|
-
UPDATE oauth_refresh_tokens
|
|
97
|
-
SET revoked = ${P}
|
|
98
|
-
WHERE access_token_id = ${z(1)}
|
|
99
|
-
`,[$]),await B.unsafe(`
|
|
100
|
-
UPDATE oauth_access_tokens
|
|
101
|
-
SET revoked = ${P}, updated_at = ${M}
|
|
102
|
-
WHERE id = ${z(1)}
|
|
103
|
-
`,[$])}async function z0($){await e0($),await B.unsafe(`
|
|
104
|
-
UPDATE oauth_access_tokens
|
|
105
|
-
SET revoked = ${P}, updated_at = ${M}
|
|
106
|
-
WHERE user_id = ${z(1)}
|
|
107
|
-
`,[$])}async function P$($){let W=await l();if(!W)return z0($);if(o)await B.unsafe(`
|
|
108
|
-
UPDATE oauth_refresh_tokens
|
|
109
|
-
SET revoked = true
|
|
110
|
-
WHERE access_token_id IN (
|
|
111
|
-
SELECT id FROM oauth_access_tokens WHERE user_id = $1 AND id != $2
|
|
112
|
-
)
|
|
113
|
-
`,[$,W.id]),await B.unsafe(`
|
|
114
|
-
UPDATE oauth_access_tokens
|
|
115
|
-
SET revoked = true, updated_at = NOW()
|
|
116
|
-
WHERE user_id = $1 AND id != $2
|
|
117
|
-
`,[$,W.id]);else await B.unsafe(`
|
|
118
|
-
UPDATE oauth_refresh_tokens
|
|
119
|
-
SET revoked = 1
|
|
120
|
-
WHERE access_token_id IN (
|
|
121
|
-
SELECT id FROM oauth_access_tokens WHERE user_id = ? AND id != ?
|
|
122
|
-
)
|
|
123
|
-
`,[$,W.id]),await B.unsafe(`
|
|
124
|
-
UPDATE oauth_access_tokens
|
|
125
|
-
SET revoked = 1, updated_at = ${M}
|
|
126
|
-
WHERE user_id = ? AND id != ?
|
|
127
|
-
`,[$,W.id])}async function v$(){await B.unsafe(`
|
|
128
|
-
DELETE FROM oauth_refresh_tokens
|
|
129
|
-
WHERE access_token_id IN (
|
|
130
|
-
SELECT id FROM oauth_access_tokens WHERE expires_at < ${M}
|
|
131
|
-
)
|
|
132
|
-
`);let $=await B.unsafe(`
|
|
133
|
-
DELETE FROM oauth_access_tokens
|
|
134
|
-
WHERE expires_at < ${M}
|
|
135
|
-
`);return $?.changes||$?.rowCount||0}async function E$($=7){let W=new Date;W.setDate(W.getDate()-$),await B.unsafe(`
|
|
136
|
-
DELETE FROM oauth_refresh_tokens
|
|
137
|
-
WHERE access_token_id IN (
|
|
138
|
-
SELECT id FROM oauth_access_tokens WHERE revoked = ${P} AND updated_at < ${z(1)}
|
|
139
|
-
)
|
|
140
|
-
`,[W.toISOString()]);let Z=await B.unsafe(`
|
|
141
|
-
DELETE FROM oauth_access_tokens
|
|
142
|
-
WHERE revoked = ${P} AND updated_at < ${z(1)}
|
|
143
|
-
`,[W.toISOString()]);return Z?.changes||Z?.rowCount||0}async function S$($){return(await B.unsafe(`
|
|
144
|
-
SELECT * FROM oauth_clients
|
|
145
|
-
WHERE user_id = ${z(1)} AND revoked = ${X0}
|
|
146
|
-
ORDER BY created_at DESC
|
|
147
|
-
`,[$])).map(w0)}async function x$($){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 R$($){let W=G0(40);if(o)await B.unsafe(`
|
|
150
|
-
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
151
|
-
VALUES ($1, $2, 'local', $3, $4, $5, false, NOW())
|
|
152
|
-
`,[$.name,W,$.redirect,$.personalAccessClient||!1,$.passwordClient||!1]);else await B.unsafe(`
|
|
153
|
-
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
154
|
-
VALUES (?, ?, 'local', ?, ?, ?, 0, ${M})
|
|
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 A$($){await B.unsafe(`
|
|
158
|
-
UPDATE oauth_clients
|
|
159
|
-
SET revoked = ${P}, updated_at = ${M}
|
|
160
|
-
WHERE id = ${z(1)}
|
|
161
|
-
`,[$])}function w($){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 L$,o0,o,FZ,M,P,X0,F$;var Y0=n0(()=>{L$=Y$.DB_CONNECTION||"sqlite",o0=K$(L$),{isPostgres:o,isMysql:FZ,now:M,boolTrue:P,boolFalse:X0}=o0;F$=l});var S1={};d0(S1,{policy:()=>F0,none:()=>M1,inspect:()=>e,hasPolicy:()=>C1,has:()=>q1,getPolicyFor:()=>U1,flush:()=>v1,denies:()=>H1,define:()=>_1,default:()=>LW,cannot:()=>P0,can:()=>C0,before:()=>z1,authorize:()=>E0,any:()=>B0,allows:()=>j1,all:()=>v0,after:()=>D1,abilities:()=>P1,Gate:()=>E1,AuthorizationResponse:()=>U,AuthorizationException:()=>V0});class U{isAllowed;message;code;constructor($,W,Z){this.isAllowed=$,this.message=W,this.code=Z}static allow($){return new U(!0,$)}static deny($,W){return new U(!1,$||"This action is unauthorized.",W)}allowed(){return this.isAllowed}denied(){return!this.isAllowed}authorize(){if(!this.isAllowed)throw new V0(this.message||"This action is unauthorized.",this.code)}}function _1($,W){q.gates.set($,W)}function F0($,W){let Z=typeof $==="string"?$:$.name;q.policies.set(Z,W)}function z1($){q.beforeCallbacks.push($)}function D1($){q.afterCallbacks.push($)}async function j1($,W,...Z){return $0($,W,...Z)}async function H1($,W,...Z){return!await $0($,W,...Z)}async function C0($,W,...Z){return $0($,W,...Z)}async function P0($,W,...Z){return!await $0($,W,...Z)}async function B0($,W,...Z){for(let J of $)if(await $0(J,W,...Z))return!0;return!1}async function v0($,W,...Z){for(let J of $)if(!await $0(J,W,...Z))return!1;return!0}async function M1($,W,...Z){return!await B0($,W,...Z)}async function E0($,W,...Z){let J=await e($,W,...Z);if(!J.isAllowed)throw new V0(J.message,J.code);return J}async function e($,W,...Z){for(let G of q.beforeCallbacks){let X=await G(W,$,Z);if(X===!0)return U.allow();if(X===!1)return U.deny()}let J=Z[0];if(J&&typeof J==="object"){let G=J.constructor?.name,X=q.policies.get(G);if(X){let K=new X;if(K.before){let L=await K.before(W,$);if(L===!0)return U.allow();if(L===!1)return U.deny()}let Y=K[$];if(Y){let L=await Y.call(K,W,...Z);return O1(L??!1)}}}let Q=q.gates.get($);if(Q){let G=await Q(W,...Z),X=O1(G);for(let K of q.afterCallbacks){let Y=await K(W,$,X.isAllowed,Z);if(typeof Y==="boolean")return Y?U.allow():U.deny()}return X}return U.deny(`No gate or policy defined for ability: ${$}`)}async function $0($,W,...Z){return(await e($,W,...Z)).isAllowed}function O1($){if($ instanceof U)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 $?U.allow():U.deny()}function U1($){if(!$||typeof $!=="object")return null;let W=$.constructor?.name,Z=q.policies.get(W);if(Z)return new Z;return null}function q1($){return q.gates.has($)}function C1($){let W=typeof $==="string"?$:$.name;return q.policies.has(W)}function P1(){return Array.from(q.gates.keys())}function v1(){q.gates.clear(),q.policies.clear(),q.beforeCallbacks=[],q.afterCallbacks=[]}var V0,q,E1,LW;var W0=n0(()=>{V0=class V0 extends Error{code;status;constructor($="This action is unauthorized.",W,Z=403){super($);this.code=W;this.status=Z;this.name="AuthorizationException"}};q={gates:new Map,policies:new Map,beforeCallbacks:[],afterCallbacks:[]};E1={define:_1,policy:F0,before:z1,after:D1,allows:j1,denies:H1,can:C0,cannot:P0,any:B0,all:v0,none:M1,authorize:E0,inspect:e,has:q1,hasPolicy:C1,abilities:P1,getPolicyFor:U1,flush:v1,AuthorizationResponse:U,AuthorizationException:V0},LW=E1});import{config as v}from"@stacksjs/config";import{db as H}from"@stacksjs/database";import{HttpError as T0}from"@stacksjs/error-handling";import{formatDate as d,User as r}from"@stacksjs/orm";import{getCurrentRequest as y$,request as f0}from"@stacksjs/router";import{Buffer as D0}from"buffer";import{createHash as N$,timingSafeEqual as I0}from"crypto";import{decrypt as W1,encrypt as w$,verifyHash as j0}from"@stacksjs/security";import{log as Z1}from"@stacksjs/logging";var t="$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";import{HttpError as $$}from"@stacksjs/error-handling";var W$=5,i0=900000,Z$=1e4,J$=300000;class y0{store=new Map;lastEviction=Date.now();evict(){let $=Date.now(),W=$-this.lastEviction>=J$,Z=this.store.size>=Z$;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 t0{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 I=new y0;class f{static useStore($){I=$}static useSharedStore(){I=new t0}static useMemoryStore(){I=new y0}static async isRateLimited($){$=$.toLowerCase();let W=Date.now(),Z=await I.get($);if(!Z)return!1;if(Z.lockedUntil>0&&Z.lockedUntil<=W)return await I.delete($),!1;return Z.lockedUntil>0}static async recordFailedAttempt($){$=$.toLowerCase();let W=Date.now(),Z=await I.get($)||{attempts:0,lockedUntil:0};if(Z.attempts++,Z.attempts>=W$)Z.lockedUntil=W+i0,Z.attempts=0;await I.set($,Z,i0)}static async resetAttempts($){await I.delete($.toLowerCase())}static async validateAttempt($){if(await this.isRateLimited($))throw new $$(429,"Too many login attempts. Please try again later.")}}Y0();var J1=Symbol.for("stacks.requestAuthState");function S(){let $=y$();if(!$)return null;let W=$[J1];if(!W)W={},$[J1]=W;return W}function K0($){return N$("sha256").update($).digest("hex")}class x{static getBearerToken(){let $=f0.bearerToken?.();if(!$){let W=f0.headers?.get?.("authorization")||f0.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 $=S();if($?.clientSecret)return $.clientSecret;let W=await this.getPersonalAccessClient();if($)$.clientSecret=W.secret;return W.secret}static async encryptTokenId($){return await w$(String($))}static async decryptTokenId($){try{return await W1($)}catch{try{let W=await this.getClientSecret();return await W1($,W)}catch{return null}}}static async getPersonalAccessClient(){try{let $=await H.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).selectAll().executeTakeFirst();if(!$)throw new T0(500,"No personal access client found. Please run `./buddy auth:setup` first.");return $}catch($){if($ instanceof Error&&$.message.includes("does not exist"))throw new T0(500,"OAuth tables not found. Please run `./buddy auth:setup` first.");throw $}}static async validateClient($,W){let Z=await H.selectFrom("oauth_clients").where("id","=",$).where("revoked","=",!1).selectAll().executeTakeFirst(),J=D0.from(W);if(!Z?.secret){let X=D0.alloc(Math.max(J.length,1)),K=J.length>0?J:D0.alloc(1);return I0(X,K),!1}let Q=String(Z.secret);if(Q.startsWith("$2"))return await j0(W,Q);let G=D0.from(Q);if(G.length!==J.length)return I0(G,G),!1;return I0(G,J)}static async getTokenFromId($){let W=await H.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:w(Z.scopes),abilities:w(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=v.auth.username||"email",Z=v.auth.password||"password",J=$[W];if(!J)return!1;let Q=await f.isRateLimited(J),G=await r.where("email","=",J).first(),X=$[Z]||"",K=G?.password||t,Y=await j0(X,K);if(Q)return!1;if(Y&&G){await f.resetAttempts(J);let L=S();if(L)L.authUser=G;return!0}return await f.recordFailedAttempt(J),!1}static async validate($){let W=v.auth.username||"email",Z=v.auth.password||"password",J=$[W];if(!J)return!1;let Q=await r.where("email","=",J).first(),G=$[Z]||"",X=Q?.password||t;return await j0(G,X)&&!!Q}static async login($,W){let Z=await this.attempt($),J=S()?.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 r.find($);if(!Z)return null;let J=S();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 H.selectFrom("oauth_access_tokens").where("token","=",K0($)).select(["id"]).executeTakeFirst();if(Z)await H.updateTable("oauth_refresh_tokens").set({revoked:!0}).where("access_token_id","=",Number(Z.id)).execute();await this.revokeToken($)}let W=S();if(W)W.authUser=void 0,W.currentToken=void 0}static async user(){let $=S();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=S();if(W)W.authUser=$}static async createTokenForUser($,W){let Z=W?.name??v.auth.defaultTokenName??"auth-token",J=W?.abilities??W?.scopes??v.auth.defaultAbilities??["*"],Q=W?.expiresInMinutes!==void 0?W.expiresInMinutes*60*1000:v.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((v.auth.refreshTokenExpiry??2592000000)/86400000));Z1.debug(`[auth] Creating token for user#${$.id}: ${Z}`);let L=await N0($.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=v.auth.defaultTokenName||"auth-token",Z=v.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 T0(401,"Invalid client credentials");let Q=await this.attempt($),G=S()?.authUser;if(!Q||!G)return null;return{token:await this.createToken(G,"user-auth-token")}}static async validateToken($){let W=K0($),Z=await H.selectFrom("oauth_access_tokens").where("token","=",W).selectAll().executeTakeFirst();if(!Z)return!1;if(Z1.debug(`[auth] Token validated for token#${Z.id}`),Z.expires_at&&new Date(String(Z.expires_at))<new Date)return await H.deleteFrom("oauth_access_tokens").where("id","=",Z.id).execute(),!1;if(Z.revoked)return!1;if(u(Z.created_at,await a(Z.user_id)))return!1;return await H.updateTable("oauth_access_tokens").set({updated_at:d(new Date)}).where("id","=",Z.id).execute(),!0}static async getUserFromToken($){let W=K0($),Z=await H.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 H.deleteFrom("oauth_access_tokens").where("id","=",Z.id).execute();return}if(Z.revoked)return;let J=S();if(J)J.currentToken=await this.getTokenFromId(Z.id)??void 0;if(await H.updateTable("oauth_access_tokens").set({updated_at:d(new Date)}).where("id","=",Z.id).execute(),!Z?.user_id)return;let Q=await r.find(Z.user_id),G=Q?.password_changed_at,X=G?new Date(String(G)):null;if(u(Z.created_at,X))return;return Q}static async currentAccessToken(){let $=S();if($?.currentToken)return $.currentToken;let W=this.getBearerToken();if(!W)return;let Z=await H.selectFrom("oauth_access_tokens").where("token","=",K0(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 H.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:w(String(J.scopes??"")),abilities:w(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 H.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:d(new Date)}).where("token","=",K0($)).execute()}static async revokeTokenById($){await H.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:d(new Date)}).where("id","=",$).execute()}static async revokeAllTokens($){let W=$??await this.id();if(!W)return;await H.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:d(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 H.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:d(new Date)}).where("user_id","=",W).where("id","!=",Z.id).execute()}static async pruneExpiredTokens(){let $=await H.deleteFrom("oauth_access_tokens").where("expires_at","<",d(new Date)).executeTakeFirst();return Number($?.numDeletedRows)||0}static async pruneRevokedTokens(){let $=await H.deleteFrom("oauth_access_tokens").where("revoked","=",!0).executeTakeFirst();return Number($?.numDeletedRows)||0}static async rotateToken($){let{findToken:W,revokeToken:Z}=await Promise.resolve().then(() => (Y0(),$1)),J=await W($);if(!J)return null;let Q=J.expiresAt?J.expiresAt.getTime()-Date.now():v.auth.tokenExpiry??3600000,G=Math.max(1,Math.floor(Q/60000));await Z($);let X=await r.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=v.auth.username||"email",Z=v.auth.password||"password",J=$[W];if(!J)return!1;let Q=await r.where("email","=",J).first(),G=$[Z]||"",X=Q?.password||t;if(await j0(G,X)&&Q){let Y=S();if(Y)Y.authUser=Q;return!0}return!1}static guard($){return this}static viaRemember(){return!1}static clearState(){let $=S();if($)$.authUser=void 0,$.currentToken=void 0,$.clientSecret=void 0}}import{generateTOTP as T$,generateTOTPSecret as f$,totpKeyUri as I$,verifyTOTP as b$}from"@stacksjs/ts-auth";function b0(){return f$()}async function xZ($){return T$({secret:$})}async function k0($,W){return b$($,{secret:W})}function Q1($,W,Z){let J=$||"johndoe@example.com",Q=W||"StacksJS 2fa",G=Z||b0();return I$(J,Q,G)}import{randomBytes as k$}from"crypto";import{db as H0}from"@stacksjs/database";import{formatDate as g$}from"@stacksjs/orm";import{err as h$,HttpError as p$,ok as m$}from"@stacksjs/error-handling";import{makeHash as c$}from"@stacksjs/security";async function fZ(){if((await H0.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).select(["id"]).executeTakeFirst())?.id)return h$({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=k$(40).toString("hex"),Z=await c$(W,{algorithm:"bcrypt"});if(await H0.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:g$(new Date)}).execute(),!(await H0.selectFrom("oauth_clients").where("secret","=",Z).select(["id"]).executeTakeFirst())?.id)throw new p$(500,"Failed to create personal access client");return m$(W)}async function u$($){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 x.getUserFromToken(W);if(!Z){let Q=Error("Invalid or expired authentication token.");throw Q.statusCode=401,Q}x.setUser(Z),$._authenticatedUser=Z;let J=await x.currentAccessToken();$._currentAccessToken=J}var kZ={name:"auth",handle:u$};import{Buffer as G1}from"buffer";import{db as k}from"@stacksjs/database";import{generateRegistrationOptions as tZ,generateAuthenticationOptions as oZ,verifyRegistrationResponse as aZ,verifyAuthenticationResponse as rZ,startRegistration as sZ,startAuthentication as eZ,browserSupportsWebAuthn as $2,browserSupportsWebAuthnAutofill as W2,platformAuthenticatorIsAvailable as Z2}from"@stacksjs/ts-auth";async function mZ($){return await k.selectFrom("passkeys").selectAll().where("user_id","=",$).execute()}async function l$($,W){return await k.selectFrom("passkeys").selectAll().where("id","=",W).where("user_id","=",$).executeTakeFirst()}async function cZ($,W,Z){let J=await l$($,W);if(!J)return!1;let Q=Number(J.counter??0);if(Z!==0&&Z<=Q)return!1;return await k.updateTable("passkeys").set({counter:Z,last_used_at:X1()}).where("id","=",W).where("user_id","=",$).execute(),!0}async function uZ($,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:X1()};await k.insertInto("passkeys").values(Q).executeTakeFirstOrThrow()}function X1(){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 d$=300;async function lZ($,W,Z,J=d$){let Q=new Date(Date.now()+J*1000).toISOString(),G=typeof W==="string"?W:G1.from(W).toString("base64url");await k.deleteFrom("webauthn_challenges").where("user_id","=",$).where("purpose","=",Z).execute(),await k.insertInto("webauthn_challenges").values({user_id:$,challenge:G,purpose:Z,expires_at:Q}).execute()}async function dZ($,W){let Z=await k.selectFrom("webauthn_challenges").where("user_id","=",$).where("purpose","=",W).selectAll().executeTakeFirst();if(!Z)return null;await k.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 G1.from(String(Z.challenge),"base64url")}import{randomBytes as $W}from"crypto";import{config as g}from"@stacksjs/config";import{db as n}from"@stacksjs/database";import{mail as U0,template as F1}from"@stacksjs/email";import{log as WW}from"@stacksjs/logging";import{formatDate as ZW}from"@stacksjs/orm";import{makeHash as K1,verifyHash as L1}from"@stacksjs/security";import{HttpError as g0}from"@stacksjs/error-handling";import{log as M0}from"@stacksjs/logging";import{User as Y1}from"@stacksjs/orm";import{verifyHash as n$}from"@stacksjs/security";import{db as T}from"@stacksjs/database";import{getCurrentRequest as i$}from"@stacksjs/router";function t$(){let $=crypto.getRandomValues(new Uint8Array(32));return Array.from($,(W)=>W.toString(16).padStart(2,"0")).join("")}function o$($){if($)return{ip:$.ip??null,userAgent:$.userAgent??null};let W=i$();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 a$($,W,Z){let J=($||"").toLowerCase(),Q=await f.isRateLimited(J),G=await Y1.where("email",$).first(),X=G?.password||t,K=await n$(W,X);if(Q)throw new g0(429,"Too many login attempts. Please try again later.");if(!K||!G)throw await f.recordFailedAttempt(J),new g0(401,"Invalid credentials");await f.resetAttempts(J);let Y=t$(),L=new Date(Date.now()+86400000),{ip:O,userAgent:_}=o$(Z);M0.debug(`[auth] Session created for user#${G.id}`);try{await T.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 M0.error(`[auth] Session persistence failed for user#${G.id}: ${E.message}`),new g0(500,"Session could not be created. Ensure the `sessions` table exists (run `./buddy migrate`).")}return{user:G,sessionId:Y}}async function r$($){M0.debug("[auth] Session destroyed");try{await T.deleteFrom("sessions").where("id","=",$).execute()}catch(W){M0.debug(`[auth] Session destroy failed: ${W.message}`)}}async function h0($){try{await T.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 p0($){try{let W=await T.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 T.deleteFrom("sessions").where("id","=",$).execute();return}return await Y1.find(W.user_id)}catch{return}}async function s$($){try{let W=await T.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 T.deleteFrom("sessions").where("id","=",$).execute(),!1;return!0}catch{return!1}}async function e$($,W=86400000){try{let Z=await T.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 T.deleteFrom("sessions").where("id","=",$).execute(),!1;let Q=new Date(Date.now()+W);return await T.updateTable("sessions").set({expires_at:Q.toISOString(),last_activity:Math.floor(Date.now()/1000)}).where("id","=",$).execute(),!0}catch{return!1}}var F2={login:a$,logout:r$,destroyAll:h0,user:p0,check:s$,refresh:e$};Y0();function m0(){return g.auth.passwordReset?.expire??60}function V1($){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=m0();return new Date(Z).getTime()+J*60000>Date.now()}return!1}async function JW($){let W=g.app.name||"Stacks",Z=g.app.supportEmail||g.email?.from?.address||"",J=new Date().toLocaleString("en-US",{dateStyle:"full",timeStyle:"short"});try{let{html:Q,text:G}=await F1("password-changed",{subject:`Your ${W} password has been changed`,variables:{changedAt:J,supportEmail:Z}});if(!Q&&!G){await U0.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 U0.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 C2($){function W(){return $W(32).toString("hex")}async function Z(){let X=W(),K=await K1(X,{algorithm:"bcrypt"}),Y=m0(),L=new Date(Date.now()+Y*60000).toISOString();return await n.deleteFrom("password_resets").where("email","=",$).execute(),await n.insertInto("password_resets").values({email:$,token:K,expires_at:L}).executeTakeFirst(),X}async function J(){if(!await n.selectFrom("users").where("email","=",$).selectAll().executeTakeFirst())return;let K=await Z(),Y=m0(),L=g.app.name||"Stacks",O=g.app.url?`https://${g.app.url}`:`http://localhost:${process.env.PORT||"3000"}`,E=(g.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:j,text:C}=await F1("password-reset",{subject:`Reset Your ${L} Password`,variables:{resetUrl:F,expireMinutes:Y}});if(!j&&!C)throw Error("password-reset template missing or rendered empty");await U0.send({to:$,subject:`Reset Your ${L} Password`,text:C,html:j})}catch(j){let C=j instanceof Error?j.message:String(j);console.warn(`[PasswordReset] template render failed, sending plain-text fallback: ${C}`),await U0.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 n.selectFrom("password_resets").where("email","=",$).selectAll().executeTakeFirst();if(!K)return!1;if(!V1(K))return await n.deleteFrom("password_resets").where("email","=",$).execute(),!1;let Y=K.token;return await L1(X,Y)}async function G(X,K){let Y=await n.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(!V1(_))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 L1(X,E))return{success:!1,message:"Invalid or expired reset token"};let j=await O.selectFrom("users").where("email","=",$).selectAll().executeTakeFirst();if(!j)return{success:!1,message:"Invalid or expired reset token"};let C=await K1(K,{algorithm:"bcrypt"});try{await O.updateTable("users").set({password:C,password_changed_at:ZW(new Date)}).where("email","=",$).executeTakeFirst()}catch(N){let R=N instanceof Error?N.message:String(N);if(/password_changed_at|no such column|unknown column/i.test(R))WW.warn("[PasswordReset] password_changed_at column missing \u2014 run `buddy migrate`; resetting without the credential-version stamp"),await O.updateTable("users").set({password:C}).where("email","=",$).executeTakeFirst();else throw N}return await O.deleteFrom("password_resets").where("email","=",$).execute(),{success:!0,userId:Number(j.id)}});if(Y.success)return await z0(Y.userId),await h0(Y.userId),JW($).catch((L)=>{console.error("[PasswordReset] Failed to send notification:",L)}),{success:!0};return Y}return{sendEmail:J,verifyToken:Q,resetPassword:G}}import{db as QW}from"@stacksjs/database";import{HttpError as q0}from"@stacksjs/error-handling";import{User as GW}from"@stacksjs/orm";import{makeHash as XW}from"@stacksjs/security";import{db as E2}from"@stacksjs/database";import{isUniqueViolation as B1}from"@stacksjs/orm";var YW=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;async function f2($){let{email:W,password:Z,name:J}=$;if(typeof W!=="string"||W.length>254||!YW.test(W))throw new q0(422,"Email address is invalid");if(typeof Z!=="string"||Z.length<8)throw new q0(422,"Password must be at least 8 characters");let Q=await XW(Z,{algorithm:"bcrypt"}),G=await QW.transaction(async(K)=>{let Y=K;if(await Y.selectFrom("users").where("email","=",W).selectAll().executeTakeFirst())throw new q0(409,"Email already exists");try{await Y.insertInto("users").values({email:W,password:Q,name:J}).execute()}catch(_){if(B1(_))throw new q0(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 GW.find(G);if(!X)throw Error("Failed to retrieve created user");return{token:await x.createToken(X,"user-auth-token")}}import{request as s}from"@stacksjs/router";async function L0(){let $=s?._authenticatedUser;if($)return $;let W=s.bearerToken?.();if(!W){let Z=s.headers?.get?.("authorization")||s.headers?.get?.("Authorization");if(Z&&Z.startsWith("Bearer "))W=Z.substring(7)}if(!W)return;return await x.getUserFromToken(W)}async function g2(){return L0()}async function KW(){return!!await L0()}async function h2(){return(await L0())?.id}async function p2(){return(await L0())?.email}async function m2(){return(await L0())?.name}async function c2(){return await KW()}async function u2(){await x.logout()}async function l2(){if(s?._authenticatedUser)s._authenticatedUser=void 0}Y0();W0();W0();W0();import{log as h}from"@stacksjs/logging";import*as S0 from"@stacksjs/path";class VW{allow($){return U.allow($)}deny($,W){return U.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 FW(){let{fs:$}=await import("@stacksjs/storage"),W=S0.appPath("Policies");if(!$.existsSync(W)){h.debug("No Policies directory found");return}try{let G=(await import(S0.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(_)F0(X,_),h.debug(`Registered policy: ${Y} for ${X}`)}}}catch{h.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)F0(G,Y),h.debug(`Auto-discovered policy: ${Q} for ${G}`)}catch(K){h.error(`Failed to load policy ${Q}:`,K)}}}async function BW(){let{define:$,before:W,after:Z}=await Promise.resolve().then(() => (W0(),S1));try{let Q=await import(S0.appPath("Gates.ts")),G=Q.gates||Q.default?.gates||{};for(let[Y,L]of Object.entries(G))if(typeof L==="function")$(Y,L),h.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);h.debug("Gates registered successfully")}catch{h.debug("No Gates.ts found or failed to load")}}async function o2(){await BW(),await FW()}W0();async function OW($,W,...Z){return C0(W,$,...Z)}async function _W($,W,...Z){return P0(W,$,...Z)}async function zW($,W,...Z){return B0(W,$,...Z)}async function DW($,W,...Z){return v0(W,$,...Z)}async function jW($,W,...Z){return E0(W,$,...Z)}async function s2($,W,...Z){return e(W,$,...Z)}function e2($){return Object.assign($,{can:(Z,...J)=>OW($,Z,...J),cannot:(Z,...J)=>_W($,Z,...J),canAny:(Z,...J)=>zW($,Z,...J),canAll:(Z,...J)=>DW($,Z,...J),authorize:(Z,...J)=>jW($,Z,...J)})}class x0{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 c0=1e4,V={userRoles:new x0(c0),userPermissions:new x0(c0),rolePermissions:new x0(c0),roles:new Map,permissions:new Map},u0=null;function HW($){u0=$,R0()}function D(){if(!u0)throw Error("RBAC store not configured. Call setRbacStore() first.");return u0}function R0(){V.userRoles.clear(),V.userPermissions.clear(),V.rolePermissions.clear(),V.roles.clear(),V.permissions.clear()}function A($){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 MW($,W="web",Z){let J=await D().createRole($,W,Z);return V.roles.set(`${$}:${W}`,J),J}async function p($,W="web"){let Z=`${$}:${W}`;if(V.roles.has(Z))return V.roles.get(Z);let J=await D().findRoleByName($,W);if(J)V.roles.set(Z,J);return J}async function UW($,W="web"){let Z=await p($,W);if(Z)await D().deleteRole(Z.id),V.roles.delete(`${$}:${W}`),R0()}async function qW($){return D().getAllRoles($)}async function CW($,W="web",Z){let J=await D().createPermission($,W,Z);return V.permissions.set(`${$}:${W}`,J),J}async function m($,W="web"){let Z=`${$}:${W}`;if(V.permissions.has(Z))return V.permissions.get(Z);let J=await D().findPermissionByName($,W);if(J)V.permissions.set(Z,J);return J}async function PW($,W="web"){let Z=await m($,W);if(Z)await D().deletePermission(Z.id),V.permissions.delete(`${$}:${W}`),R0()}async function vW($){return D().getAllPermissions($)}async function Z0($){let W=A($);if(V.userRoles.has(W))return V.userRoles.get(W);let Z=await D().getUserRoles(W);return V.userRoles.set(W,Z),Z}async function x1($,W,Z="web"){let J=A($),Q=await p(W,Z);if(!Q)throw Error(`Role '${W}' not found.`);await D().assignRoleToUser(J,Q.id),V.userRoles.delete(J),V.userPermissions.delete(J)}async function R1($,W,Z="web"){let J=A($),Q=await p(W,Z);if(!Q)return;await D().removeRoleFromUser(J,Q.id),V.userRoles.delete(J),V.userPermissions.delete(J)}async function EW($){let W=A($);await D().removeAllRolesFromUser(W),V.userRoles.delete(W),V.userPermissions.delete(W)}async function A1($,W,Z="web"){let J=A($),Q=[];for(let G of W){let X=await p(G,Z);if(!X)throw Error(`Role '${G}' not found.`);Q.push(X.id)}await D().syncUserRoles(J,Q),V.userRoles.delete(J),V.userPermissions.delete(J)}async function y1($,W,Z="web"){return(await Z0($)).some((Q)=>Q.name===W&&Q.guard_name===Z)}async function N1($,W,Z="web"){let J=await Z0($);return W.some((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function w1($,W,Z="web"){let J=await Z0($);return W.every((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function O0($){let W=A($);if(V.userPermissions.has(W))return V.userPermissions.get(W);let Z=await D().getUserDirectPermissions(W),J=await Z0($),Q=[];for(let K of J){let Y=await h1(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 T1($,W,Z="web"){let J=A($),Q=await m(W,Z);if(!Q)throw Error(`Permission '${W}' not found.`);await D().assignPermissionToUser(J,Q.id),V.userPermissions.delete(J)}async function f1($,W,Z="web"){let J=A($),Q=await m(W,Z);if(!Q)return;await D().removePermissionFromUser(J,Q.id),V.userPermissions.delete(J)}async function SW($){let W=A($);await D().removeAllPermissionsFromUser(W),V.userPermissions.delete(W)}async function I1($,W,Z="web"){let J=A($),Q=[];for(let G of W){let X=await m(G,Z);if(!X)throw Error(`Permission '${G}' not found.`);Q.push(X.id)}await D().syncUserPermissions(J,Q),V.userPermissions.delete(J)}async function b1($,W,Z="web"){return(await O0($)).some((Q)=>Q.name===W&&Q.guard_name===Z)}async function k1($,W,Z="web"){let J=await O0($);return W.some((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function g1($,W,Z="web"){let J=await O0($);return W.every((Q)=>J.some((G)=>G.name===Q&&G.guard_name===Z))}async function h1($){if(V.rolePermissions.has($))return V.rolePermissions.get($);let W=await D().getRolePermissions($);return V.rolePermissions.set($,W),W}async function xW($,W,Z="web"){let J=await p($,Z);if(!J)throw Error(`Role '${$}' not found.`);let Q=await m(W,Z);if(!Q)throw Error(`Permission '${W}' not found.`);await D().assignPermissionToRole(J.id,Q.id),V.rolePermissions.delete(J.id),V.userPermissions.clear()}async function RW($,W,Z="web"){let J=await p($,Z);if(!J)return;let Q=await m(W,Z);if(!Q)return;await D().removePermissionFromRole(J.id,Q.id),V.rolePermissions.delete(J.id),V.userPermissions.clear()}async function AW($,W,Z="web"){let J=await p($,Z);if(!J)throw Error(`Role '${$}' not found.`);let Q=[];for(let G of W){let X=await m(G,Z);if(!X)throw Error(`Permission '${G}' not found.`);Q.push(X.id)}await D().syncRolePermissions(J.id,Q),V.rolePermissions.delete(J.id),V.userPermissions.clear()}function yW($){let W=A($);return Object.assign($,{hasRole:(J,Q)=>y1(W,J,Q),hasAnyRole:(J,Q)=>N1(W,J,Q),hasAllRoles:(J,Q)=>w1(W,J,Q),hasPermission:(J,Q)=>b1(W,J,Q),hasAnyPermission:(J,Q)=>k1(W,J,Q),hasAllPermissions:(J,Q)=>g1(W,J,Q),getRoles:()=>Z0(W),getPermissions:()=>O0(W),assignRole:(J,Q)=>x1(W,J,Q),removeRole:(J,Q)=>R1(W,J,Q),syncRoles:(J,Q)=>A1(W,J,Q),givePermission:(J,Q)=>T1(W,J,Q),revokePermission:(J,Q)=>f1(W,J,Q),syncPermissions:(J,Q)=>I1(W,J,Q)})}var W3={setStore:HW,flushCache:R0,createRole:MW,findRole:p,deleteRole:UW,getAllRoles:qW,createPermission:CW,findPermission:m,deletePermission:PW,getAllPermissions:vW,getUserRoles:Z0,assignRole:x1,removeRole:R1,removeAllRoles:EW,syncRoles:A1,hasRole:y1,hasAnyRole:N1,hasAllRoles:w1,getUserPermissions:O0,givePermission:T1,revokePermission:f1,revokeAllPermissions:SW,syncPermissions:I1,hasPermission:b1,hasAnyPermission:k1,hasAllPermissions:g1,getRolePermissions:h1,givePermissionToRole:xW,revokePermissionFromRole:RW,syncRolePermissions:AW,withRbac:yW};import{Buffer as p1}from"buffer";import{createHmac as c1,randomBytes as NW,timingSafeEqual as wW}from"crypto";import{config as J0}from"@stacksjs/config";import{db as i}from"@stacksjs/database";import{mail as m1,template as TW}from"@stacksjs/email";import{log as fW}from"@stacksjs/logging";function u1(){let $=J0.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 IW($){let W=NW(32).toString("hex"),Z=`${$}:${W}`,J=c1("sha256",u1()).update(Z).digest("hex");return{token:W,hash:J}}function bW($,W,Z){let J=`${$}:${W}`,Q=c1("sha256",u1()).update(J).digest("hex"),G=p1.from(Q),X=p1.from(Z);if(G.length!==X.length)return!1;return wW(G,X)}function kW(){let W=(J0.auth??{}).emailVerification;if(W!=null&&typeof W==="object"){let Z=W;if(typeof Z.expire==="number")return Z.expire}return 60}function gW($,W){let Z=J0.app.url?`https://${J0.app.url}`:`http://localhost:${process.env.PORT||"3000"}`,Q=(J0.auth.emailVerification?.url??"/verify-email/{id}/{token}").replace("{id}",String($)).replace("{token}",W);return/^https?:\/\//.test(Q)?Q:`${Z}${Q.startsWith("/")?"":"/"}${Q}`}function l1($){return $.email_verified_at!=null}async function d1($){let{token:W,hash:Z}=IW($.id),J=kW(),Q=new Date(Date.now()+J*60*1000);await i.deleteFrom("email_verifications").where("user_id","=",$.id).execute(),await i.insertInto("email_verifications").values({user_id:$.id,token:Z,expires_at:Q.toISOString()}).executeTakeFirst();let G=gW($.id,W),X=J0.app.name||"Stacks";try{let{html:K,text:Y}=await TW("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 m1.send({to:$.email,subject:`Verify Your ${X} Email Address`,text:Y,html:K})}catch(K){let Y=K instanceof Error?K.message:String(K);fW.warn(`[email] Email verification template failed, using plain text fallback: ${Y}`),await m1.send({to:$.email,subject:`Verify Your ${X} Email Address`,text:`Please verify your email address by visiting: ${G}
|
|
164
|
-
|
|
165
|
-
This link expires in ${J} minutes.`})}}async function hW($,W){let Z=await i.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 i.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!1,message:"Verification link has expired. Please request a new one."};if(!bW($,W,Z.token))return{success:!1,message:"Invalid verification link."};return await i.updateTable("users").set({email_verified_at:new Date().toISOString()}).where("id","=",$).executeTakeFirst(),await i.deleteFrom("email_verifications").where("user_id","=",$).execute(),{success:!0,message:"Email verified successfully."}}async function pW($){if(l1($))return{success:!1,message:"Email is already verified."};let W=await i.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 d1($),{success:!0,message:"Verification email sent."}}var L3={isVerified:l1,send:d1,verify:hW,resend:pW};import{randomBytes as mW}from"crypto";import{db as y}from"@stacksjs/database";var cW=300;async function n1($){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 uW($){return Boolean($.two_factor_enabled)}function lW($,W){let Z=b0(),J=Q1($,W,Z);return{secret:Z,uri:J}}var dW=600;async function nW($,W,Z=dW){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 iW($){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 tW($,W,Z){if(!await k0(Z,W))return!1;return await y.updateTable("users").set({two_factor_secret:W,two_factor_enabled:!0}).where("id","=",$).executeTakeFirst(),!0}async function oW($){await y.updateTable("users").set({two_factor_secret:null,two_factor_enabled:!1}).where("id","=",$).executeTakeFirst()}async function aW($,W){let{secret:Z,enabled:J}=await n1($);if(!J||!Z)return!1;return k0(W,Z)}async function rW($,W=cW){let Z=mW(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 sW($){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 _3={isEnabled:uW,getState:n1,generateSetup:lW,stashPendingSecret:nW,consumePendingSecret:iW,enable:tW,disable:oW,verifyLoginCode:aW,createChallenge:rW,consumeChallenge:sW};import{config as c}from"@stacksjs/config";import{db as A0}from"@stacksjs/database";var l0="active_team";function i1($){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 t1($){let W=$.cookies?.get(l0);if(!W)return null;let Z=Number(W);return Number.isFinite(Z)&&Z>0?Z:null}function U3($,W={}){let Z=Math.max(0,Math.floor(W.maxAgeSeconds??31536000)),J=[`${l0}=${encodeURIComponent(String($))}`,"Path=/","HttpOnly","SameSite=Lax",`Max-Age=${Z}`];if(W.secure)J.push("Secure");return J.join("; ")}function q3(){return`${l0}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`}async function eW($){let W=await $Z($);if(!W?.id)return null;let Z=await A0.selectFrom("team_members").where("user_id","=",W.id).where("status","=","active").select(["team_id","role"]).execute();if(Z.length===0)return null;let J=i1({memberships:Z.map((Q)=>({team_id:Number(Q.team_id),role:String(Q.role)})),activeTeamId:t1($)});return J.teamId!=null?{teamId:J.teamId,role:J.role??String(Z[0].role)}:null}async function C3($,W={}){let Z={user:null,teamId:null,role:null,teams:[],activeTeamId:null},J=c.auth?.default||"api",G=(c.auth?.guards?.[J]?.driver||"token")==="session"?await o1($):await a1($);if(!G?.id)return Z;let X=await A0.selectFrom("team_members").where("user_id","=",G.id).where("status","=","active").select(["team_id","role"]).execute(),K=W.allowAnyTeam?!!W.allowAnyTeam(G):!1,Y=t1($),L=i1({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 A0.selectFrom("teams").select(["id","name"]).execute();else{let F=[...O.keys()];_=F.length?await A0.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,j)=>F.name.localeCompare(j.name));return{user:G,teamId:L.teamId,role:L.role,teams:E,activeTeamId:Y}}async function P3($){let W=await eW($);return W?W.teamId:null}async function $Z($){let W=c.auth?.default||"api",Q=((c.auth?.guards?.[W]||{driver:"token"}).driver||"token")==="session"?await o1($):await a1($);return Q?.id?Q:void 0}async function o1($){let W=$.cookies?.get("session_id");if(!W)return;return p0(W)}async function a1($){let W=c.auth?.defaultTokenName||"auth-token",Z=(typeof $.bearerToken==="function"?$.bearerToken():void 0)??$.cookies?.get(W);if(!Z)return;return x.getUserFromToken(Z)}export{yW as withRbac,e2 as withAuthorization,aW as verifyTwoFactorLoginCode,k0 as verifyTwoFactorCode,o3 as verifyTOTP,aZ as verifyRegistrationResponse,hW as verifyEmail,rZ as verifyAuthenticationResponse,j$ as validateRefreshToken,_W as userCannot,zW as userCanAny,DW as userCanAll,OW as userCan,cZ as updatePasskeyCounter,r3 as totpKeyUri,V$ as tokens,B$ as tokenCant,_$ as tokenCanAny,O$ as tokenCanAll,s0 as tokenCan,z$ as tokenAbilities,F$ as token,A1 as syncRoles,AW as syncRolePermissions,I1 as syncPermissions,lZ as storeWebAuthnChallenge,nW as stashPendingTwoFactorSecret,sZ as startRegistration,eZ as startAuthentication,HW as setRbacStore,uZ as setCurrentRegistrationOptions,p0 as sessionUser,e$ as sessionRefresh,r$ as sessionLogout,a$ as sessionLogin,h0 as sessionDestroyAll,s$ as sessionCheck,d1 as sendVerificationEmail,i1 as selectActiveTeam,l3 as seedDefaultRoles,C$ as revokeTokenById,q$ as revokeToken,H$ as revokeRefreshToken,RW as revokePermissionFromRole,f1 as revokePermission,P$ as revokeOtherTokens,A$ as revokeClient,z0 as revokeAllTokens,e0 as revokeAllRefreshTokens,SW as revokeAllPermissions,C3 as resolveTeamContext,$Z as resolveAuthenticatedUser,P3 as resolveAuthenticatedTeamId,eW as resolveAuthenticatedMembership,pW as resendVerificationEmail,R1 as removeRole,EW as removeAllRoles,BW as registerGates,f2 as register,D$ as refreshToken,l2 as refresh,F0 as policy,Z2 as platformAuthenticatorIsAvailable,C2 as passwordResets,w as parseScopes,M1 as none,m2 as name,u2 as logout,uW as isTwoFactorEnabled,u as isIssuedBeforePasswordChange,l1 as isEmailVerified,c2 as isAuthenticated,s2 as inspectUser,e as inspect,o2 as initializeAuthorization,h2 as id,y1 as hasRole,C1 as hasPolicy,b1 as hasPermission,N1 as hasAnyRole,k1 as hasAnyPermission,w1 as hasAllRoles,g1 as hasAllPermissions,q1 as has,xW as givePermissionToRole,T1 as givePermission,Z0 as getUserRoles,O0 as getUserPermissions,mZ as getUserPasskeys,l$ as getUserPasskey,n1 as getTwoFactorState,h1 as getRolePermissions,U1 as getPolicyFor,a as getPasswordChangedAt,g2 as getCurrentUser,qW as getAllRoles,vW as getAllPermissions,t1 as getActiveTeamPreference,Q1 as generateTwoFactorUri,xZ as generateTwoFactorToken,lW as generateTwoFactorSetup,b0 as generateTwoFactorSecret,a3 as generateTOTPSecret,t3 as generateTOTP,tZ as generateRegistrationOptions,oZ as generateAuthenticationOptions,R0 as flushRbacCache,v1 as flush,r0 as findToken,p as findRole,m as findPermission,x$ as findClient,tW as enableTwoFactor,p2 as email,FW as discoverPolicies,oW as disableTwoFactor,H1 as denies,UW as deleteRole,E$ as deleteRevokedTokens,U$ as deleteRevokedRefreshTokens,PW as deletePermission,v$ as deleteExpiredTokens,M$ as deleteExpiredRefreshTokens,_1 as define,l as currentAccessToken,rW as createTwoFactorChallenge,N0 as createToken,MW as createRole,fZ as createPersonalAccessClient,CW as createPermission,R$ as createClient,m3 as createBqbRbacStore,dZ as consumeWebAuthnChallenge,sW as consumeTwoFactorChallenge,iW as consumePendingTwoFactorSecret,S$ as clients,q3 as clearActiveTeamCookie,KW as check,P0 as cannot,C0 as can,U3 as buildActiveTeamCookie,W2 as browserSupportsWebAuthnAutofill,$2 as browserSupportsWebAuthn,z1 as before,jW as authorizeUser,E0 as authorize,L0 as authUser,kZ as authMiddlewareHandler,u$ as authMiddleware,x1 as assignRole,B0 as any,j1 as allows,v0 as all,D1 as after,P1 as abilities,_3 as TwoFactor,F2 as SessionAuth,W3 as Rbac,f as RateLimiter,E1 as Gate,L3 as EmailVerification,u3 as DEFAULT_ROLE_PACKS,VW as BasePolicy,U as AuthorizationResponse,V0 as AuthorizationException,x as Auth,l0 as ACTIVE_TEAM_COOKIE};
|
|
1
|
+
export * from "./authentication";
|
|
2
|
+
export * from "./authenticator";
|
|
3
|
+
export * from "./client";
|
|
4
|
+
export * from "./middleware";
|
|
5
|
+
export * from "./rate-limiter";
|
|
6
|
+
export * from "./passkey";
|
|
7
|
+
export * from "./password/reset";
|
|
8
|
+
export * from "./register";
|
|
9
|
+
export * from "./user";
|
|
10
|
+
export * from "./tokens";
|
|
11
|
+
export * from "./gate";
|
|
12
|
+
export * from "./policy";
|
|
13
|
+
export * from "./authorizable";
|
|
14
|
+
export * from "./rbac";
|
|
15
|
+
export { createBqbRbacStore } from "./rbac-store-bqb";
|
|
16
|
+
export { DEFAULT_ROLE_PACKS, seedDefaultRoles } from "./rbac-seed";
|
|
17
|
+
export * from "./email-verification";
|
|
18
|
+
export * from "./session-auth";
|
|
19
|
+
export {
|
|
20
|
+
generateTOTP,
|
|
21
|
+
verifyTOTP,
|
|
22
|
+
generateTOTPSecret,
|
|
23
|
+
totpKeyUri
|
|
24
|
+
} from "@stacksjs/ts-auth";
|
|
25
|
+
export * from "./two-factor";
|
|
26
|
+
export * from "./team";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DUMMY_BCRYPT_HASH = "$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Auth } from "./authentication";
|
|
2
|
+
export async function authMiddleware(request) {
|
|
3
|
+
let bearerToken = request.bearerToken?.();
|
|
4
|
+
if (!bearerToken) {
|
|
5
|
+
const authHeader = request.headers?.get?.("authorization") || request.headers?.get?.("Authorization");
|
|
6
|
+
if (authHeader && authHeader.startsWith("Bearer "))
|
|
7
|
+
bearerToken = authHeader.substring(7);
|
|
8
|
+
}
|
|
9
|
+
if (!bearerToken) {
|
|
10
|
+
const error = Error("No authentication token provided.");
|
|
11
|
+
error.statusCode = 401;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
const user = await Auth.getUserFromToken(bearerToken);
|
|
15
|
+
if (!user) {
|
|
16
|
+
const error = Error("Invalid or expired authentication token.");
|
|
17
|
+
error.statusCode = 401;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
Auth.setUser(user);
|
|
21
|
+
request._authenticatedUser = user;
|
|
22
|
+
const accessToken = await Auth.currentAccessToken();
|
|
23
|
+
request._currentAccessToken = accessToken;
|
|
24
|
+
}
|
|
25
|
+
export const authMiddlewareHandler = {
|
|
26
|
+
name: "auth",
|
|
27
|
+
handle: authMiddleware
|
|
28
|
+
};
|
package/dist/passkey.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { db } from "@stacksjs/database";
|
|
3
|
+
import { User } from "@stacksjs/orm";
|
|
4
|
+
export {
|
|
5
|
+
generateRegistrationOptions,
|
|
6
|
+
generateAuthenticationOptions,
|
|
7
|
+
verifyRegistrationResponse,
|
|
8
|
+
verifyAuthenticationResponse,
|
|
9
|
+
startRegistration,
|
|
10
|
+
startAuthentication,
|
|
11
|
+
browserSupportsWebAuthn,
|
|
12
|
+
browserSupportsWebAuthnAutofill,
|
|
13
|
+
platformAuthenticatorIsAvailable
|
|
14
|
+
} from "@stacksjs/ts-auth";
|
|
15
|
+
export async function getUserPasskeys(userId) {
|
|
16
|
+
return await db.selectFrom("passkeys").selectAll().where("user_id", "=", userId).execute();
|
|
17
|
+
}
|
|
18
|
+
export async function getUserPasskey(userId, passkeyId) {
|
|
19
|
+
return await db.selectFrom("passkeys").selectAll().where("id", "=", passkeyId).where("user_id", "=", userId).executeTakeFirst();
|
|
20
|
+
}
|
|
21
|
+
export async function updatePasskeyCounter(userId, passkeyId, newCounter) {
|
|
22
|
+
const passkey = await getUserPasskey(userId, passkeyId);
|
|
23
|
+
if (!passkey)
|
|
24
|
+
return !1;
|
|
25
|
+
const stored = Number(passkey.counter ?? 0);
|
|
26
|
+
if (newCounter <= stored && !(newCounter === 0 && stored === 0))
|
|
27
|
+
return !1;
|
|
28
|
+
await db.updateTable("passkeys").set({ counter: newCounter, last_used_at: formatDateTime() }).where("id", "=", passkeyId).where("user_id", "=", userId).execute();
|
|
29
|
+
return !0;
|
|
30
|
+
}
|
|
31
|
+
export async function setCurrentRegistrationOptions(user, verified) {
|
|
32
|
+
const credentialId = verified.registrationInfo?.credential.id, credentialPublicKey = verified.registrationInfo?.credential.publicKey;
|
|
33
|
+
if (!credentialId)
|
|
34
|
+
throw Error("[auth/passkey] WebAuthn registration response is missing credential.id");
|
|
35
|
+
if (!credentialPublicKey)
|
|
36
|
+
throw Error("[auth/passkey] WebAuthn registration response is missing credential.publicKey");
|
|
37
|
+
const passkeyData = {
|
|
38
|
+
id: credentialId,
|
|
39
|
+
cred_public_key: JSON.stringify(credentialPublicKey),
|
|
40
|
+
user_id: user.id,
|
|
41
|
+
webauthn_user_id: user.email || "",
|
|
42
|
+
counter: verified.registrationInfo?.credential.counter || 0,
|
|
43
|
+
credential_type: verified.registrationInfo?.credentialType || "",
|
|
44
|
+
device_type: verified.registrationInfo?.credentialDeviceType || "",
|
|
45
|
+
backup_eligible: !1,
|
|
46
|
+
backup_status: verified.registrationInfo?.credentialBackedUp || !1,
|
|
47
|
+
transports: JSON.stringify(["internal"]),
|
|
48
|
+
last_used_at: formatDateTime()
|
|
49
|
+
};
|
|
50
|
+
await db.insertInto("passkeys").values(passkeyData).executeTakeFirstOrThrow();
|
|
51
|
+
}
|
|
52
|
+
function formatDateTime() {
|
|
53
|
+
const date = new Date, pad = (num) => String(num).padStart(2, "0"), year = date.getFullYear(), month = pad(date.getMonth() + 1), day = pad(date.getDate()), hours = pad(date.getHours()), minutes = pad(date.getMinutes()), seconds = pad(date.getSeconds());
|
|
54
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
55
|
+
}
|
|
56
|
+
const DEFAULT_CHALLENGE_TTL_SECONDS = 300;
|
|
57
|
+
export async function storeWebAuthnChallenge(userId, challenge, purpose, ttlSeconds = DEFAULT_CHALLENGE_TTL_SECONDS) {
|
|
58
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString(), encodedChallenge = typeof challenge === "string" ? challenge : Buffer.from(challenge).toString("base64url");
|
|
59
|
+
await db.deleteFrom("webauthn_challenges").where("user_id", "=", userId).where("purpose", "=", purpose).execute();
|
|
60
|
+
await db.insertInto("webauthn_challenges").values({
|
|
61
|
+
user_id: userId,
|
|
62
|
+
challenge: encodedChallenge,
|
|
63
|
+
purpose,
|
|
64
|
+
expires_at: expiresAt
|
|
65
|
+
}).execute();
|
|
66
|
+
}
|
|
67
|
+
export async function consumeWebAuthnChallenge(userId, purpose) {
|
|
68
|
+
const row = await db.selectFrom("webauthn_challenges").where("user_id", "=", userId).where("purpose", "=", purpose).selectAll().executeTakeFirst();
|
|
69
|
+
if (!row)
|
|
70
|
+
return null;
|
|
71
|
+
await db.deleteFrom("webauthn_challenges").where("user_id", "=", userId).where("purpose", "=", purpose).execute();
|
|
72
|
+
const expiresAt = row.expires_at ? new Date(String(row.expires_at)).getTime() : 0;
|
|
73
|
+
if (Date.now() > expiresAt)
|
|
74
|
+
return null;
|
|
75
|
+
return Buffer.from(String(row.challenge), "base64url");
|
|
76
|
+
}
|