@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
package/dist/team.d.ts ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Pure resolver — no I/O — that decides which team is "active" for a user
3
+ * given their memberships and an optional switch preference. Reusable across
4
+ * apps: it encodes the whole workspace-switching precedence in one place.
5
+ *
6
+ * - Honors `activeTeamId` only when the user may access it: a member always
7
+ * may; a privileged operator may access any team when `allowAnyTeam` is set
8
+ * (the app decides who is privileged — e.g. a super-admin flag).
9
+ * - Otherwise falls back to the highest-priority membership (owner > admin >
10
+ * anything else), matching what dashboard pages have always shown, so the
11
+ * "current team" never differs between a rendered form and the action it
12
+ * posts to.
13
+ */
14
+ export declare function selectActiveTeam(opts: {
15
+ memberships: Array<{ team_id: number | string, role?: string | null }>
16
+ activeTeamId?: number | null
17
+ allowAnyTeam?: boolean
18
+ rolePriority?: Record<string, number>
19
+ }): { teamId: number | null, role: string | null };
20
+ /** Read the active-team switch preference from a request's cookie. */
21
+ export declare function getActiveTeamPreference(request: TeamAuthRequest): number | null;
22
+ /**
23
+ * Build the `Set-Cookie` that pins the active team (workspace switcher). A
24
+ * year-long HttpOnly cookie: only the server needs it (SSR reads it, the
25
+ * switch action writes it), and it carries no privilege of its own.
26
+ */
27
+ export declare function buildActiveTeamCookie(teamId: number, opts?: { maxAgeSeconds?: number, secure?: boolean }): string;
28
+ /** Clear the active-team cookie {@link buildActiveTeamCookie} sets. */
29
+ export declare function clearActiveTeamCookie(): string;
30
+ /**
31
+ * Resolve the requesting user's active team membership (team id + role)
32
+ * from their auth credential. Driver-aware: reads config/auth.ts's
33
+ * configured guard driver rather than hardcoding a validation scheme.
34
+ *
35
+ * Dashboard forms are plain HTML POSTs (no client JS, no Authorization
36
+ * header) — the browser only ever sends the auth cookie set on login,
37
+ * so that's checked for the 'token' driver alongside a bearer-header
38
+ * fallback for JS/API callers.
39
+ *
40
+ * Owner membership wins over admin, which wins over any other active
41
+ * membership, when a user belongs to more than one team — the same
42
+ * precedence server-rendered dashboard pages use, so a user never sees
43
+ * a different "current team" between the page that rendered a form and
44
+ * the action that form posts to.
45
+ *
46
+ * Returns `null` when unauthenticated or without an active team
47
+ * membership — callers must treat that as "reject the request," not
48
+ * "fall back to a default team."
49
+ */
50
+ export declare function resolveAuthenticatedMembership(request: TeamAuthRequest): Promise<TeamMembershipResult | null>;
51
+ /**
52
+ * Full team context for a server-rendered dashboard: the authenticated user,
53
+ * the resolved active team (honoring the workspace switcher), and the list of
54
+ * teams the user can switch between. One call replaces the per-page auth+team
55
+ * boilerplate every dashboard view used to copy, and centralizes the
56
+ * switch-aware scoping so pages and the actions they post to never disagree.
57
+ *
58
+ * `opts.allowAnyTeam(user)` lets an app grant an operator (e.g. a super-admin)
59
+ * access to every team — they can switch to, and see, teams they aren't a
60
+ * member of. The full user row is returned so the app can read its own
61
+ * columns (like a super-admin flag) without a second query.
62
+ */
63
+ export declare function resolveTeamContext(request: TeamAuthRequest, opts?: { allowAnyTeam?: (user: any) => boolean }): Promise<TeamContext>;
64
+ /**
65
+ * Team id only — the common case for actions that just need to scope a
66
+ * write to the requester's team. See {@link resolveAuthenticatedMembership}
67
+ * when the caller also needs the role (e.g. owner/admin-only settings).
68
+ */
69
+ export declare function resolveAuthenticatedTeamId(request: TeamAuthRequest): Promise<number | null>;
70
+ /**
71
+ * The authenticated USER (not team) from a request's real auth
72
+ * credential — bearer header first, then the login cookie, driver-aware
73
+ * like {@link resolveAuthenticatedMembership} (which builds on this).
74
+ * For dashboard form actions that operate on the requester themselves
75
+ * (security settings, profile) rather than on team-scoped rows: plain
76
+ * HTML POSTs carry no Authorization header, so `request.user()` (stamped
77
+ * by the auth middleware from a bearer/session) is undefined there and
78
+ * the login cookie is the only credential available.
79
+ */
80
+ export declare function resolveAuthenticatedUser(request: TeamAuthRequest): Promise<{ id: number, email?: string } | undefined>;
81
+ /**
82
+ * Name of the cookie that remembers which team the user last switched the
83
+ * dashboard to (a workspace switcher). It is NOT a security credential: the
84
+ * server re-validates membership against `selectActiveTeam` on every request,
85
+ * so a tampered value can only ever resolve to a team the user already
86
+ * belongs to (or, for operators, any team when `allowAnyTeam` is set).
87
+ */
88
+ export declare const ACTIVE_TEAM_COOKIE: 'active_team';
89
+ /**
90
+ * Team resolution from a request's real auth credential (bearer token or
91
+ * session cookie) — never from a client-supplied form field. Extracted
92
+ * from app-land (stacksjs/status config/auth-team.ts) because every app
93
+ * with team-scoped dashboard forms needs exactly this, and `config/` is
94
+ * for autoloaded config files, not shared helpers.
95
+ *
96
+ * Structural request type on purpose: callers pass whatever request
97
+ * object their action received. Partial objects (tests, non-HTTP
98
+ * callers) resolve to "unauthenticated" rather than crashing.
99
+ */
100
+ export declare interface TeamAuthRequest {
101
+ bearerToken?: () => string | null | undefined
102
+ cookies?: { get: (name: string) => string | null | undefined }
103
+ }
104
+ export declare interface TeamMembershipResult {
105
+ teamId: number
106
+ role: string
107
+ }
108
+ /** A team the authenticated user may switch the dashboard to. */
109
+ export declare interface SwitchableTeam {
110
+ id: number
111
+ name: string
112
+ role: string
113
+ }
114
+ /** Full dashboard team context — see {@link resolveTeamContext}. */
115
+ export declare interface TeamContext {
116
+ user: any | null
117
+ teamId: number | null
118
+ role: string | null
119
+ teams: SwitchableTeam[]
120
+ activeTeamId: number | null
121
+ }
package/dist/team.js ADDED
@@ -0,0 +1,88 @@
1
+ import { config } from "@stacksjs/config";
2
+ import { db } from "@stacksjs/database";
3
+ import { Auth } from "./authentication";
4
+ import { sessionUser } from "./session-auth";
5
+ export const ACTIVE_TEAM_COOKIE = "active_team";
6
+ export function selectActiveTeam(opts) {
7
+ const priority = opts.rolePriority ?? { owner: 0, admin: 1 }, sorted = [...opts.memberships ?? []].sort((a, b) => (priority[a.role ?? ""] ?? 2) - (priority[b.role ?? ""] ?? 2)), active = opts.activeTeamId == null ? null : Number(opts.activeTeamId);
8
+ if (active != null && Number.isFinite(active)) {
9
+ const member = sorted.find((x) => Number(x.team_id) === active);
10
+ if (member)
11
+ return { teamId: active, role: member.role ?? null };
12
+ if (opts.allowAnyTeam)
13
+ return { teamId: active, role: "admin" };
14
+ }
15
+ const first = sorted[0];
16
+ return first ? { teamId: Number(first.team_id), role: first.role ?? null } : { teamId: null, role: null };
17
+ }
18
+ export function getActiveTeamPreference(request) {
19
+ const raw = request.cookies?.get(ACTIVE_TEAM_COOKIE);
20
+ if (!raw)
21
+ return null;
22
+ const n = Number(raw);
23
+ return Number.isFinite(n) && n > 0 ? n : null;
24
+ }
25
+ export function buildActiveTeamCookie(teamId, opts = {}) {
26
+ const maxAge = Math.max(0, Math.floor(opts.maxAgeSeconds ?? 31536000)), parts = [`${ACTIVE_TEAM_COOKIE}=${encodeURIComponent(String(teamId))}`, "Path=/", "HttpOnly", "SameSite=Lax", `Max-Age=${maxAge}`];
27
+ if (opts.secure)
28
+ parts.push("Secure");
29
+ return parts.join("; ");
30
+ }
31
+ export function clearActiveTeamCookie() {
32
+ return `${ACTIVE_TEAM_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
33
+ }
34
+ export async function resolveAuthenticatedMembership(request) {
35
+ const user = await resolveAuthenticatedUser(request);
36
+ if (!user?.id)
37
+ return null;
38
+ const memberships = await db.selectFrom("team_members").where("user_id", "=", user.id).where("status", "=", "active").select(["team_id", "role"]).execute();
39
+ if (memberships.length === 0)
40
+ return null;
41
+ const picked = selectActiveTeam({
42
+ memberships: memberships.map((m) => ({
43
+ team_id: Number(m.team_id),
44
+ role: m.role == null ? null : String(m.role)
45
+ })),
46
+ activeTeamId: getActiveTeamPreference(request)
47
+ });
48
+ return picked.teamId != null ? { teamId: picked.teamId, role: picked.role ?? "" } : null;
49
+ }
50
+ export async function resolveTeamContext(request, opts = {}) {
51
+ const empty = { user: null, teamId: null, role: null, teams: [], activeTeamId: null }, guardName = config.auth?.default || "api", user = (config.auth?.guards?.[guardName]?.driver || "token") === "session" ? await resolveSessionUser(request) : await resolveTokenUser(request);
52
+ if (!user?.id)
53
+ return empty;
54
+ const memberships = await db.selectFrom("team_members").where("user_id", "=", user.id).where("status", "=", "active").select(["team_id", "role"]).execute(), allowAny = opts.allowAnyTeam ? !!opts.allowAnyTeam(user) : !1, activeTeamId = getActiveTeamPreference(request), picked = selectActiveTeam({
55
+ memberships: memberships.map((m) => ({ team_id: Number(m.team_id), role: String(m.role) })),
56
+ activeTeamId,
57
+ allowAnyTeam: allowAny
58
+ }), roleByTeam = new Map(memberships.map((m) => [Number(m.team_id), String(m.role)]));
59
+ let teamRows = [];
60
+ if (allowAny)
61
+ teamRows = await db.selectFrom("teams").select(["id", "name"]).execute();
62
+ else {
63
+ const ids = [...roleByTeam.keys()];
64
+ teamRows = ids.length ? await db.selectFrom("teams").whereIn("id", ids).select(["id", "name"]).execute() : [];
65
+ }
66
+ const teams = teamRows.map((t) => ({ id: Number(t.id), name: String(t.name), role: roleByTeam.get(Number(t.id)) || "viewer" })).sort((a, b) => a.name.localeCompare(b.name));
67
+ return { user, teamId: picked.teamId, role: picked.role, teams, activeTeamId };
68
+ }
69
+ export async function resolveAuthenticatedTeamId(request) {
70
+ const membership = await resolveAuthenticatedMembership(request);
71
+ return membership ? membership.teamId : null;
72
+ }
73
+ export async function resolveAuthenticatedUser(request) {
74
+ const guardName = config.auth?.default || "api", user = ((config.auth?.guards?.[guardName] || { driver: "token" }).driver || "token") === "session" ? await resolveSessionUser(request) : await resolveTokenUser(request);
75
+ return user?.id ? user : void 0;
76
+ }
77
+ async function resolveSessionUser(request) {
78
+ const sessionId = request.cookies?.get("session_id");
79
+ if (!sessionId)
80
+ return;
81
+ return sessionUser(sessionId);
82
+ }
83
+ async function resolveTokenUser(request) {
84
+ const cookieName = config.auth?.defaultTokenName || "auth-token", bearer = (typeof request.bearerToken === "function" ? request.bearerToken() : void 0) ?? request.cookies?.get(cookieName);
85
+ if (!bearer)
86
+ return;
87
+ return Auth.getUserFromToken(bearer);
88
+ }
@@ -0,0 +1,16 @@
1
+ export type { AuthToken } from '@stacksjs/types';
2
+ /**
3
+ * `TokenManager` used to expose `createAccessToken` / `validateToken` /
4
+ * `rotateToken` / `revokeToken` static methods that hardcoded a 30-day
5
+ * `expires_at`, bypassed `config.auth.tokenExpiry`, and compared raw
6
+ * tokens against DB-stored hashes (i.e. broken). They had no callers —
7
+ * the real path lives on `Auth` (see authentication.ts) and respects
8
+ * the configured 1-hour expiry plus the refresh-token rotation
9
+ * landed for #1839. Removed entirely so the trap can't fire.
10
+ *
11
+ * `generateJWT` is the one piece worth keeping — `Auth.createTokenForUser`
12
+ * and `Auth.rotateToken` both use it to mint the JWT body.
13
+ */
14
+ export declare class TokenManager {
15
+ static generateJWT(userId: number, expiresInSeconds?: number): string;
16
+ }
package/dist/token.js ADDED
@@ -0,0 +1,21 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createHmac, randomBytes } from "node:crypto";
3
+ import process from "node:process";
4
+
5
+ export class TokenManager {
6
+ static generateJWT(userId, expiresInSeconds = 3600) {
7
+ const appKey = process.env.APP_KEY;
8
+ if (!appKey)
9
+ throw Error("APP_KEY is not set. JWT tokens cannot be generated without a secure application key.");
10
+ const header = {
11
+ alg: "HS256",
12
+ typ: "JWT"
13
+ }, nowSeconds = Math.floor(Date.now() / 1000), payload = {
14
+ sub: userId,
15
+ iat: nowSeconds,
16
+ exp: nowSeconds + expiresInSeconds,
17
+ jti: randomBytes(16).toString("hex")
18
+ }, encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url"), encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url"), signature = createHmac("sha256", appKey).update(`${encodedHeader}.${encodedPayload}`).digest("base64url");
19
+ return `${encodedHeader}.${encodedPayload}.${signature}`;
20
+ }
21
+ }
@@ -0,0 +1,284 @@
1
+ import type { AccessToken, CreateClientOptions, CreateClientResult, DatabaseDriver, OAuthClient, PersonalAccessTokenResult, RefreshTokenResult, TokenScopes } from '@stacksjs/types';
2
+ /**
3
+ * Read `users.password_changed_at` for a user.
4
+ *
5
+ * Binds a token's validity to the account's credential state: a token
6
+ * issued before the user last changed their password is no longer
7
+ * trusted, regardless of its own `revoked`/`expires_at` flags. This is
8
+ * the durable, use-time backstop behind the post-reset revocation sweep
9
+ * (#1947) — even a freshly minted pair that the sweep never saw is
10
+ * rejected on first use.
11
+ *
12
+ * Returns `null` on ANY error (missing column / missing table) so a
13
+ * not-yet-migrated database degrades to legacy-allow rather than locking
14
+ * everyone out. Accepts an optional query runner so the refresh exchange
15
+ * can read the stamp inside its own transaction.
16
+ */
17
+ export declare function getPasswordChangedAt(userId: unknown, q?: { unsafe: (sql: string, params?: any[]) => any }): Promise<Date | null>;
18
+ /**
19
+ * True when a credential issued at `createdAt` predates the user's last
20
+ * password change (`changedAt`) and must therefore be rejected.
21
+ *
22
+ * Legacy-allow semantics:
23
+ * - `changedAt` null (no stamp / un-migrated) => never reject.
24
+ * - `createdAt` missing/unparseable => never reject.
25
+ *
26
+ * Strict `<` so a token minted in the SAME second as (or after) the
27
+ * reset — e.g. the victim's immediate post-reset login — is NOT bricked
28
+ * by CURRENT_TIMESTAMP's one-second granularity.
29
+ */
30
+ export declare function isIssuedBeforePasswordChange(createdAt: unknown, changedAt: Date | null): boolean;
31
+ /**
32
+ * Get all access tokens for a user
33
+ *
34
+ * @example
35
+ * import { tokens } from '@stacksjs/auth'
36
+ * const userTokens = await tokens(user.id)
37
+ */
38
+ export declare function tokens(userId: number): Promise<AccessToken[]>;
39
+ /**
40
+ * Get a specific token by its plain text value
41
+ * Uses hash comparison for security
42
+ *
43
+ * @example
44
+ * import { findToken } from '@stacksjs/auth'
45
+ * const token = await findToken('abc123...')
46
+ */
47
+ export declare function findToken(plainTextToken: string): Promise<AccessToken | null>;
48
+ /**
49
+ * Get the current access token from the request context
50
+ *
51
+ * @example
52
+ * import { currentAccessToken } from '@stacksjs/auth'
53
+ * const token = await currentAccessToken()
54
+ */
55
+ export declare function currentAccessToken(): Promise<AccessToken | null>;
56
+ /**
57
+ * Check if the current token has a given scope/ability
58
+ *
59
+ * @example
60
+ * import { tokenCan } from '@stacksjs/auth'
61
+ * if (await tokenCan('posts:create')) {
62
+ * // user can create posts
63
+ * }
64
+ */
65
+ export declare function tokenCan(scope: string): Promise<boolean>;
66
+ /**
67
+ * Check if the current token does NOT have a given scope/ability
68
+ *
69
+ * @example
70
+ * import { tokenCant } from '@stacksjs/auth'
71
+ * if (await tokenCant('admin')) {
72
+ * throw new Error('Admin access required')
73
+ * }
74
+ */
75
+ export declare function tokenCant(scope: string): Promise<boolean>;
76
+ /**
77
+ * Check if token has ALL of the given scopes
78
+ *
79
+ * @example
80
+ * import { tokenCanAll } from '@stacksjs/auth'
81
+ * if (await tokenCanAll(['posts:read', 'posts:write'])) {
82
+ * // user has both scopes
83
+ * }
84
+ */
85
+ export declare function tokenCanAll(scopes: string[]): Promise<boolean>;
86
+ /**
87
+ * Check if token has ANY of the given scopes
88
+ *
89
+ * @example
90
+ * import { tokenCanAny } from '@stacksjs/auth'
91
+ * if (await tokenCanAny(['admin', 'moderator'])) {
92
+ * // user has at least one of the scopes
93
+ * }
94
+ */
95
+ export declare function tokenCanAny(scopes: string[]): Promise<boolean>;
96
+ /**
97
+ * Get all scopes/abilities for the current token
98
+ *
99
+ * @example
100
+ * import { tokenAbilities } from '@stacksjs/auth'
101
+ * const abilities = await tokenAbilities()
102
+ * // ['read', 'write', 'posts:create']
103
+ */
104
+ export declare function tokenAbilities(): Promise<string[]>;
105
+ /**
106
+ * Create a new personal access token for a user
107
+ * Tokens are hashed before storage for security
108
+ *
109
+ * @param userId - The user ID to create the token for
110
+ * @param name - A name/description for the token
111
+ * @param scopes - Array of scopes/abilities for the token
112
+ * @param options - Additional options
113
+ * @param options.expiresInMinutes - Token expiry in minutes (default: 60)
114
+ * @param options.withRefreshToken - Whether to create a refresh token (default: true)
115
+ * @param options.refreshExpiresInDays - Refresh token expiry in days (default: 30)
116
+ *
117
+ * @example
118
+ * import { createToken } from '@stacksjs/auth'
119
+ * const result = await createToken(user.id, 'My API Token', ['read', 'write'])
120
+ * console.log(result.plainTextToken) // Save this - it won't be shown again!
121
+ * console.log(result.refreshToken) // Use this to get new access tokens
122
+ */
123
+ export declare function createToken(userId: number, name?: string, scopes?: string[], options?: {
124
+ expiresInMinutes?: number
125
+ withRefreshToken?: boolean
126
+ refreshExpiresInDays?: number
127
+ }): Promise<PersonalAccessTokenResult>;
128
+ /**
129
+ * Exchange a refresh token for a new access token
130
+ *
131
+ * @param refreshTokenPlain - The plain text refresh token
132
+ * @param options - Additional options
133
+ * @param options.expiresInMinutes - New access token expiry in minutes (default: 60)
134
+ * @param options.refreshExpiresInDays - New refresh token expiry in days (default: 30)
135
+ *
136
+ * @example
137
+ * import { refreshToken } from '@stacksjs/auth'
138
+ * const result = await refreshToken('your-refresh-token')
139
+ * // Use result.plainTextToken as new access token
140
+ * // Use result.refreshToken as new refresh token (old one is revoked)
141
+ */
142
+ export declare function refreshToken(refreshTokenPlain: string, options?: {
143
+ expiresInMinutes?: number
144
+ refreshExpiresInDays?: number
145
+ }): Promise<RefreshTokenResult>;
146
+ /**
147
+ * Validate a refresh token without exchanging it
148
+ *
149
+ * @example
150
+ * import { validateRefreshToken } from '@stacksjs/auth'
151
+ * const isValid = await validateRefreshToken('your-refresh-token')
152
+ */
153
+ export declare function validateRefreshToken(refreshTokenPlain: string): Promise<boolean>;
154
+ /**
155
+ * Revoke a specific refresh token
156
+ *
157
+ * @example
158
+ * import { revokeRefreshToken } from '@stacksjs/auth'
159
+ * await revokeRefreshToken('your-refresh-token')
160
+ */
161
+ export declare function revokeRefreshToken(refreshTokenPlain: string): Promise<void>;
162
+ /**
163
+ * Revoke all refresh tokens for a user
164
+ *
165
+ * @example
166
+ * import { revokeAllRefreshTokens } from '@stacksjs/auth'
167
+ * await revokeAllRefreshTokens(user.id)
168
+ */
169
+ export declare function revokeAllRefreshTokens(userId: number): Promise<void>;
170
+ /**
171
+ * Delete expired refresh tokens (cleanup)
172
+ *
173
+ * @example
174
+ * import { deleteExpiredRefreshTokens } from '@stacksjs/auth'
175
+ * const count = await deleteExpiredRefreshTokens()
176
+ */
177
+ export declare function deleteExpiredRefreshTokens(): Promise<number>;
178
+ /**
179
+ * Delete revoked refresh tokens older than specified days
180
+ *
181
+ * @example
182
+ * import { deleteRevokedRefreshTokens } from '@stacksjs/auth'
183
+ * const count = await deleteRevokedRefreshTokens(7)
184
+ */
185
+ export declare function deleteRevokedRefreshTokens(daysOld?: number): Promise<number>;
186
+ /**
187
+ * Revoke a specific access token
188
+ *
189
+ * @example
190
+ * import { revokeToken } from '@stacksjs/auth'
191
+ * await revokeToken('abc123...')
192
+ */
193
+ export declare function revokeToken(plainTextToken: string): Promise<void>;
194
+ /**
195
+ * Revoke a token by its ID
196
+ *
197
+ * @example
198
+ * import { revokeTokenById } from '@stacksjs/auth'
199
+ * await revokeTokenById(123)
200
+ */
201
+ export declare function revokeTokenById(tokenId: number): Promise<void>;
202
+ /**
203
+ * Revoke all tokens for a user
204
+ *
205
+ * @example
206
+ * import { revokeAllTokens } from '@stacksjs/auth'
207
+ * await revokeAllTokens(user.id)
208
+ */
209
+ export declare function revokeAllTokens(userId: number): Promise<void>;
210
+ /**
211
+ * Revoke all tokens except the current one
212
+ *
213
+ * @example
214
+ * import { revokeOtherTokens } from '@stacksjs/auth'
215
+ * await revokeOtherTokens(user.id)
216
+ */
217
+ export declare function revokeOtherTokens(userId: number): Promise<void>;
218
+ /**
219
+ * Delete expired tokens (cleanup)
220
+ *
221
+ * @example
222
+ * import { deleteExpiredTokens } from '@stacksjs/auth'
223
+ * const count = await deleteExpiredTokens()
224
+ */
225
+ export declare function deleteExpiredTokens(): Promise<number>;
226
+ /**
227
+ * Delete revoked tokens older than specified days
228
+ *
229
+ * @example
230
+ * import { deleteRevokedTokens } from '@stacksjs/auth'
231
+ * const count = await deleteRevokedTokens(30) // older than 30 days
232
+ */
233
+ export declare function deleteRevokedTokens(daysOld?: number): Promise<number>;
234
+ /**
235
+ * Get all OAuth clients for a user
236
+ *
237
+ * @example
238
+ * import { clients } from '@stacksjs/auth'
239
+ * const userClients = await clients(user.id)
240
+ */
241
+ export declare function clients(userId: number): Promise<OAuthClient[]>;
242
+ /**
243
+ * Get a specific OAuth client by ID
244
+ *
245
+ * @example
246
+ * import { findClient } from '@stacksjs/auth'
247
+ * const client = await findClient(1)
248
+ */
249
+ export declare function findClient(clientId: number): Promise<OAuthClient | null>;
250
+ /**
251
+ * Create a new OAuth client
252
+ *
253
+ * @example
254
+ * import { createClient } from '@stacksjs/auth'
255
+ * const client = await createClient({
256
+ * name: 'My App',
257
+ * redirect: 'https://myapp.com/callback'
258
+ * })
259
+ */
260
+ export declare function createClient(options: CreateClientOptions): Promise<CreateClientResult>;
261
+ /**
262
+ * Revoke an OAuth client
263
+ *
264
+ * @example
265
+ * import { revokeClient } from '@stacksjs/auth'
266
+ * await revokeClient(1)
267
+ */
268
+ export declare function revokeClient(clientId: number): Promise<void>;
269
+ // ============================================================================
270
+ // HELPER FUNCTIONS
271
+ // ============================================================================
272
+ export declare function parseScopes(scopes: string | string[] | null | undefined): TokenScopes;
273
+ /** Current database driver */
274
+ declare const dbDriver: DatabaseDriver;
275
+ /** Cross-database SQL helpers */
276
+ declare const sql: unknown;
277
+ /**
278
+ * Alias for currentAccessToken
279
+ *
280
+ * @example
281
+ * import { token } from '@stacksjs/auth'
282
+ * const t = await token()
283
+ */
284
+ export declare const token: unknown;