@stacksjs/auth 0.70.88 → 0.70.91

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.
Files changed (47) hide show
  1. package/dist/authentication.d.ts +43 -0
  2. package/dist/authentication.js +413 -0
  3. package/dist/authenticator.d.ts +17 -0
  4. package/dist/authenticator.js +14 -0
  5. package/dist/authorizable.d.ts +77 -0
  6. package/dist/authorizable.js +28 -0
  7. package/dist/client.d.ts +22 -0
  8. package/dist/client.js +26 -0
  9. package/dist/email-verification.d.ts +29 -0
  10. package/dist/email-verification.js +111 -0
  11. package/dist/gate.d.ts +180 -0
  12. package/dist/gate.js +203 -0
  13. package/dist/index.d.ts +36 -0
  14. package/dist/index.js +26 -0
  15. package/dist/internal-constants.d.ts +19 -0
  16. package/dist/internal-constants.js +1 -0
  17. package/dist/middleware.d.ts +14 -0
  18. package/dist/middleware.js +28 -0
  19. package/dist/passkey.d.ts +97 -0
  20. package/dist/passkey.js +76 -0
  21. package/dist/password/reset.d.ts +10 -0
  22. package/dist/password/reset.js +156 -0
  23. package/dist/policy.d.ts +33 -0
  24. package/dist/policy.js +91 -0
  25. package/dist/rate-limiter.d.ts +44 -0
  26. package/dist/rate-limiter.js +91 -0
  27. package/dist/rbac-seed.d.ts +24 -0
  28. package/dist/rbac-seed.js +30 -0
  29. package/dist/rbac-store-bqb.d.ts +18 -0
  30. package/dist/rbac-store-bqb.js +180 -0
  31. package/dist/rbac.d.ts +265 -0
  32. package/dist/rbac.js +324 -0
  33. package/dist/register.d.ts +3 -0
  34. package/dist/register.js +43 -0
  35. package/dist/session-auth.d.ts +67 -0
  36. package/dist/session-auth.js +151 -0
  37. package/dist/team.d.ts +121 -0
  38. package/dist/team.js +88 -0
  39. package/dist/token.d.ts +16 -0
  40. package/dist/token.js +21 -0
  41. package/dist/tokens.d.ts +284 -0
  42. package/dist/tokens.js +489 -0
  43. package/dist/two-factor.d.ts +67 -0
  44. package/dist/two-factor.js +113 -0
  45. package/dist/user.d.ts +34 -0
  46. package/dist/user.js +41 -0
  47. package/package.json +3 -3
@@ -0,0 +1,111 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
3
+ import { config } from "@stacksjs/config";
4
+ import { db } from "@stacksjs/database";
5
+ import { mail, template } from "@stacksjs/email";
6
+ import { log } from "@stacksjs/logging";
7
+ function getVerificationKey() {
8
+ const appKey = config.app.key;
9
+ if (typeof appKey !== "string" || appKey.length === 0)
10
+ 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.");
11
+ return appKey;
12
+ }
13
+ function generateVerificationToken(userId) {
14
+ const nonce = randomBytes(32).toString("hex"), payload = `${userId}:${nonce}`, hash = createHmac("sha256", getVerificationKey()).update(payload).digest("hex");
15
+ return { token: nonce, hash };
16
+ }
17
+ function verifyToken(userId, token, storedHash) {
18
+ const payload = `${userId}:${token}`, hash = createHmac("sha256", getVerificationKey()).update(payload).digest("hex"), a = Buffer.from(hash), b = Buffer.from(storedHash);
19
+ if (a.length !== b.length)
20
+ return !1;
21
+ return timingSafeEqual(a, b);
22
+ }
23
+ function getExpiryMinutes() {
24
+ const emailVerification = (config.auth ?? {}).emailVerification;
25
+ if (emailVerification != null && typeof emailVerification === "object") {
26
+ const ev = emailVerification;
27
+ if (typeof ev.expire === "number")
28
+ return ev.expire;
29
+ }
30
+ return 60;
31
+ }
32
+ function getVerificationUrl(userId, token) {
33
+ const base = config.app.url ? `https://${config.app.url}` : `http://localhost:${process.env.PORT || "3000"}`, filled = (config.auth.emailVerification?.url ?? "/verify-email/{id}/{token}").replace("{id}", String(userId)).replace("{token}", token);
34
+ return /^https?:\/\//.test(filled) ? filled : `${base}${filled.startsWith("/") ? "" : "/"}${filled}`;
35
+ }
36
+ export function isEmailVerified(user) {
37
+ return user.email_verified_at != null;
38
+ }
39
+ export async function sendVerificationEmail(user) {
40
+ const { token, hash } = generateVerificationToken(user.id), expiryMinutes = getExpiryMinutes(), expiresAt = new Date(Date.now() + expiryMinutes * 60 * 1000);
41
+ await db.deleteFrom("email_verifications").where("user_id", "=", user.id).execute();
42
+ await db.insertInto("email_verifications").values({
43
+ user_id: user.id,
44
+ token: hash,
45
+ expires_at: expiresAt.toISOString()
46
+ }).executeTakeFirst();
47
+ const verificationUrl = getVerificationUrl(user.id, token), appName = config.app.name || "Stacks";
48
+ try {
49
+ const { html, text } = await template("email-verification", {
50
+ subject: `Verify Your ${appName} Email Address`,
51
+ variables: {
52
+ verificationUrl,
53
+ expiryMinutes,
54
+ userName: user.name || user.email
55
+ }
56
+ });
57
+ if (!html && !text)
58
+ throw Error("email-verification template missing or rendered empty");
59
+ await mail.send({
60
+ to: user.email,
61
+ subject: `Verify Your ${appName} Email Address`,
62
+ text,
63
+ html
64
+ });
65
+ } catch (templateError) {
66
+ const errorMessage = templateError instanceof Error ? templateError.message : String(templateError);
67
+ log.warn(`[email] Email verification template failed, using plain text fallback: ${errorMessage}`);
68
+ await mail.send({
69
+ to: user.email,
70
+ subject: `Verify Your ${appName} Email Address`,
71
+ text: `Please verify your email address by visiting: ${verificationUrl}
72
+
73
+ This link expires in ${expiryMinutes} minutes.`
74
+ });
75
+ }
76
+ }
77
+ export async function verifyEmail(userId, token) {
78
+ const record = await db.selectFrom("email_verifications").where("user_id", "=", userId).selectAll().executeTakeFirst();
79
+ if (!record)
80
+ return { success: !1, message: "No verification request found. Please request a new verification email." };
81
+ const expiresAt = new Date(record.expires_at);
82
+ if (Number.isNaN(expiresAt.getTime()) || new Date > expiresAt) {
83
+ await db.deleteFrom("email_verifications").where("user_id", "=", userId).execute();
84
+ return { success: !1, message: "Verification link has expired. Please request a new one." };
85
+ }
86
+ if (!verifyToken(userId, token, record.token))
87
+ return { success: !1, message: "Invalid verification link." };
88
+ await db.updateTable("users").set({ email_verified_at: new Date().toISOString() }).where("id", "=", userId).executeTakeFirst();
89
+ await db.deleteFrom("email_verifications").where("user_id", "=", userId).execute();
90
+ return { success: !0, message: "Email verified successfully." };
91
+ }
92
+ export async function resendVerificationEmail(user) {
93
+ if (isEmailVerified(user))
94
+ return { success: !1, message: "Email is already verified." };
95
+ const existing = await db.selectFrom("email_verifications").where("user_id", "=", user.id).selectAll().executeTakeFirst();
96
+ if (existing) {
97
+ const createdAt = new Date(existing.created_at), secondsSince = (Date.now() - createdAt.getTime()) / 1000;
98
+ if (Number.isNaN(secondsSince))
99
+ return { success: !1, message: "Please wait a moment before requesting another verification email." };
100
+ if (secondsSince < 60)
101
+ return { success: !1, message: `Please wait ${Math.ceil(60 - secondsSince)} seconds before requesting another verification email.` };
102
+ }
103
+ await sendVerificationEmail(user);
104
+ return { success: !0, message: "Verification email sent." };
105
+ }
106
+ export const EmailVerification = {
107
+ isVerified: isEmailVerified,
108
+ send: sendVerificationEmail,
109
+ verify: verifyEmail,
110
+ resend: resendVerificationEmail
111
+ };
package/dist/gate.d.ts ADDED
@@ -0,0 +1,180 @@
1
+ import type { UserModel as OrmUserModel } from '@stacksjs/orm';
2
+ /**
3
+ * Define a new authorization gate
4
+ *
5
+ * @example
6
+ * define('edit-settings', (user) => user?.isAdmin)
7
+ * define('update-post', (user, post) => user?.id === post.userId)
8
+ */
9
+ export declare function define<T = any>(ability: string, callback: GateCallback<T>): void;
10
+ /**
11
+ * Register a policy for a model
12
+ *
13
+ * @example
14
+ * policy('Post', PostPolicy)
15
+ * policy(Post, PostPolicy)
16
+ */
17
+ export declare function policy(model: string | { name: string }, policyClass: new () => Policy): void;
18
+ /**
19
+ * Register a callback to run before all gate checks
20
+ *
21
+ * @example
22
+ * before((user, _ability) => {
23
+ * if (user?.isSuperAdmin) return true // Super admins can do anything
24
+ * return null // Continue to normal checks
25
+ * })
26
+ */
27
+ export declare function before(callback: (user: UserModel | null, ability: string, args: any[]) => boolean | null | Promise<boolean | null>): void;
28
+ /**
29
+ * Register a callback to run after all gate checks
30
+ */
31
+ export declare function after(callback: (user: UserModel | null, ability: string, result: boolean, args: any[]) => boolean | void | Promise<boolean | void>): void;
32
+ /**
33
+ * Check if the user is allowed to perform an ability
34
+ *
35
+ * @example
36
+ * if (await allows('edit-settings', user)) { ... }
37
+ * if (await allows('update', user, post)) { ... }
38
+ */
39
+ export declare function allows(ability: string, user: UserModel | null, ...args: any[]): Promise<boolean>;
40
+ /**
41
+ * Check if the user is denied from performing an ability
42
+ *
43
+ * @example
44
+ * if (await denies('delete', user, post)) { ... }
45
+ */
46
+ export declare function denies(ability: string, user: UserModel | null, ...args: any[]): Promise<boolean>;
47
+ /**
48
+ * Check if the user can perform an ability (alias for allows)
49
+ */
50
+ export declare function can(ability: string, user: UserModel | null, ...args: any[]): Promise<boolean>;
51
+ /**
52
+ * Check if the user cannot perform an ability (alias for denies)
53
+ */
54
+ export declare function cannot(ability: string, user: UserModel | null, ...args: any[]): Promise<boolean>;
55
+ /**
56
+ * Check if the user can perform any of the given abilities
57
+ *
58
+ * @example
59
+ * if (await any(['update', 'delete'], user, post)) { ... }
60
+ */
61
+ export declare function any(abilities: string[], user: UserModel | null, ...args: any[]): Promise<boolean>;
62
+ /**
63
+ * Check if the user can perform all of the given abilities
64
+ *
65
+ * @example
66
+ * if (await all(['view', 'update'], user, post)) { ... }
67
+ */
68
+ export declare function all(abilities: string[], user: UserModel | null, ...args: any[]): Promise<boolean>;
69
+ /**
70
+ * Check if the user can perform none of the given abilities
71
+ */
72
+ export declare function none(abilities: string[], user: UserModel | null, ...args: any[]): Promise<boolean>;
73
+ /**
74
+ * Authorize an ability or throw an exception
75
+ *
76
+ * @example
77
+ * await authorize('update', user, post) // Throws if not allowed
78
+ */
79
+ export declare function authorize(ability: string, user: UserModel | null, ...args: any[]): Promise<AuthorizationResponse>;
80
+ /**
81
+ * Get detailed inspection result for an ability check
82
+ */
83
+ export declare function inspect(ability: string, user: UserModel | null, ...args: any[]): Promise<AuthorizationResponse>;
84
+ /**
85
+ * Get a policy instance for a model
86
+ */
87
+ export declare function getPolicyFor<T = any>(model: T): Policy<T> | null;
88
+ /**
89
+ * Check if a gate is defined
90
+ */
91
+ export declare function has(ability: string): boolean;
92
+ /**
93
+ * Check if a policy is registered for a model
94
+ */
95
+ export declare function hasPolicy(model: string | { name: string }): boolean;
96
+ /**
97
+ * Get all defined gate names
98
+ */
99
+ export declare function abilities(): string[];
100
+ /**
101
+ * Clear all gates and policies (useful for testing)
102
+ */
103
+ export declare function flush(): void;
104
+ /**
105
+ * Gate facade for convenient access
106
+ */
107
+ export declare const Gate: {
108
+ define: typeof define;
109
+ policy: typeof policy;
110
+ before: typeof before;
111
+ after: typeof after;
112
+ allows: typeof allows;
113
+ denies: typeof denies;
114
+ can: typeof can;
115
+ cannot: typeof cannot;
116
+ any: typeof any;
117
+ all: typeof all;
118
+ none: typeof none;
119
+ authorize: typeof authorize;
120
+ inspect: typeof inspect;
121
+ has: typeof has;
122
+ hasPolicy: typeof hasPolicy;
123
+ abilities: typeof abilities;
124
+ getPolicyFor: typeof getPolicyFor;
125
+ flush: typeof flush;
126
+ AuthorizationResponse: typeof AuthorizationResponse;
127
+ AuthorizationException: typeof AuthorizationException
128
+ };
129
+ /**
130
+ * Policy class interface
131
+ */
132
+ export declare interface Policy<T = any> {
133
+ before?(user: UserModel | null, ability: string): boolean | null | Promise<boolean | null>
134
+ viewAny?(user: UserModel | null): boolean | Promise<boolean> | AuthorizationResponse
135
+ view?(user: UserModel | null, model: T): boolean | Promise<boolean> | AuthorizationResponse
136
+ create?(user: UserModel | null): boolean | Promise<boolean> | AuthorizationResponse
137
+ update?(user: UserModel | null, model: T): boolean | Promise<boolean> | AuthorizationResponse
138
+ delete?(user: UserModel | null, model: T): boolean | Promise<boolean> | AuthorizationResponse
139
+ restore?(user: UserModel | null, model: T): boolean | Promise<boolean> | AuthorizationResponse
140
+ forceDelete?(user: UserModel | null, model: T): boolean | Promise<boolean> | AuthorizationResponse
141
+ [key: string]: PolicyMethod | undefined
142
+ }
143
+ // Alias the ORM-derived UserModel under the name this module uses internally.
144
+ // The gate API receives authenticated user objects (rows / instances),
145
+ // not the User class constructor.
146
+ declare type UserModel = OrmUserModel;
147
+ /**
148
+ * Gate callback function type
149
+ */
150
+ export type GateCallback<T = any> = (_user: UserModel | null, ..._args: T[]) => boolean | Promise<boolean> | AuthorizationResponse;
151
+ /**
152
+ * Policy method type. The return type intentionally allows `null` so that
153
+ * a policy's `before()` hook (which returns `null` to delegate to the
154
+ * underlying ability check) is index-compatible with the catch-all
155
+ * `[key: string]: PolicyMethod | undefined` signature on `Policy`.
156
+ */
157
+ export type PolicyMethod<T = any> = (_user: UserModel | null, _model?: T, ..._args: any[]) => boolean | null | Promise<boolean | null> | AuthorizationResponse;
158
+ /**
159
+ * Authorization response for detailed allow/deny
160
+ */
161
+ export declare class AuthorizationResponse {
162
+ readonly isAllowed: boolean;
163
+ readonly message?: string;
164
+ readonly code?: string;
165
+ constructor(allowed: boolean, message?: string, code?: string);
166
+ static allow(message?: string): AuthorizationResponse;
167
+ static deny(message?: string, code?: string): AuthorizationResponse;
168
+ allowed(): boolean;
169
+ denied(): boolean;
170
+ authorize(): void;
171
+ }
172
+ /**
173
+ * Authorization exception
174
+ */
175
+ export declare class AuthorizationException extends Error {
176
+ public readonly code?: string;
177
+ public readonly status?: number;
178
+ constructor(message?: string, code?: string, status?: number);
179
+ }
180
+ export default Gate;
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;
@@ -0,0 +1,36 @@
1
+ export type { SeedDefaultRolesResult } from './rbac-seed';
2
+ export * from './authentication';
3
+ export * from './authenticator';
4
+ export * from './client';
5
+ export * from './middleware';
6
+ export * from './rate-limiter';
7
+ // WebAuthn/Passkey support (now using ts-auth - no external dependencies)
8
+ export * from './passkey';
9
+ export * from './password/reset';
10
+ export * from './register';
11
+ export * from './user';
12
+ // Token management (Laravel Passport-style)
13
+ export * from './tokens';
14
+ // Authorization Gates & Policies (Laravel-style)
15
+ export * from './gate';
16
+ export * from './policy';
17
+ export * from './authorizable';
18
+ // Role-Based Access Control (RBAC)
19
+ export * from './rbac';
20
+ export { createBqbRbacStore } from './rbac-store-bqb';
21
+ export { DEFAULT_ROLE_PACKS, seedDefaultRoles } from './rbac-seed';
22
+ // Email Verification
23
+ export * from './email-verification';
24
+ // Session-based Authentication (SPA Cookie Auth)
25
+ export * from './session-auth';
26
+ // TOTP (Two-Factor Authentication) - re-export from ts-auth
27
+ export {
28
+ generateTOTP,
29
+ verifyTOTP,
30
+ generateTOTPSecret,
31
+ totpKeyUri,
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';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
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,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';
@@ -0,0 +1 @@
1
+ export const DUMMY_BCRYPT_HASH = "$2b$12$000000000000000000000uGByljkdFkOJRCRiYZGFOAstyLlSgTSW";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Built-in auth middleware handler
3
+ * Validates bearer token and sets the authenticated user on Auth
4
+ */
5
+ export declare function authMiddleware(request: any): Promise<void>;
6
+ /**
7
+ * Auth middleware object with handle method (for compatibility with middleware loader)
8
+ * @defaultValue `{ name: 'auth' }`
9
+ */
10
+ export declare const authMiddlewareHandler: {
11
+ /** @defaultValue 'auth' */
12
+ name: string;
13
+ handle: unknown
14
+ };
@@ -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
+ };