@stacksjs/auth 0.70.177 → 0.70.179

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.
@@ -141,8 +141,10 @@ export class Auth {
141
141
  return await verifyHash(authPass, hashToVerify) && !!user;
142
142
  }
143
143
  static async login(credentials, options) {
144
- const isValid = await this.attempt(credentials), authedUser = authStateOrNull()?.authUser;
145
- if (!isValid || !authedUser)
144
+ if (!await this.attempt(credentials))
145
+ return null;
146
+ const username = config.auth.username || "email", authedUser = authStateOrNull()?.authUser ?? await User.where(username, "=", credentials[username]).first();
147
+ if (!authedUser)
146
148
  return null;
147
149
  const { plainTextToken, refreshToken, expiresIn } = await this.createTokenForUser(authedUser, options);
148
150
  return { user: authedUser, token: plainTextToken, refreshToken, expiresIn };
@@ -0,0 +1,70 @@
1
+ import { Auth } from './authentication';
2
+ /**
3
+ * The `Set-Cookie` value that signs a browser in.
4
+ *
5
+ * Pair it with the token from `Auth.login()`:
6
+ *
7
+ * ```ts
8
+ * const result = await Auth.login({ email, password })
9
+ * return new Response(null, {
10
+ * status: 303,
11
+ * headers: { 'Location': '/account', 'Set-Cookie': authCookie(result.token) },
12
+ * })
13
+ * ```
14
+ */
15
+ export declare function authCookie(token: string, options?: AuthCookieOptions): string;
16
+ /**
17
+ * The `Set-Cookie` value that signs a browser out.
18
+ *
19
+ * Every attribute except the value has to match the cookie being replaced, or
20
+ * the browser keeps the original alongside the expired one.
21
+ */
22
+ export declare function clearAuthCookie(options?: AuthCookieOptions): string;
23
+ /** The raw token in a request's auth cookie, if it carries one. */
24
+ export declare function authCookieToken(request: Request | { headers: Headers }, options?: AuthCookieOptions): string | undefined;
25
+ /**
26
+ * The signed-in user for a server-rendered request, or undefined.
27
+ *
28
+ * Reads the cookie, then validates the token exactly as a bearer token would
29
+ * be — a revoked or expired token yields undefined, so signing out on one
30
+ * device takes effect on every page load elsewhere.
31
+ */
32
+ export declare function userFromCookie(request: Request | { headers: Headers }, options?: AuthCookieOptions): Promise<AuthenticatedUser>;
33
+ /** Whether a server-rendered request carries a valid session. */
34
+ export declare function cookieCheck(request: Request | { headers: Headers }, options?: AuthCookieOptions): Promise<boolean>;
35
+ /**
36
+ * Sign out the browser: revoke the token the cookie carries, then clear it.
37
+ *
38
+ * Revoking matters — without it a copied cookie stays valid for the token's
39
+ * whole lifetime, and "log out on a shared computer" would only mean "hide
40
+ * the key".
41
+ */
42
+ export declare function logoutCookie(request: Request | { headers: Headers }, options?: AuthCookieOptions): Promise<string>;
43
+ /**
44
+ * Cookie-carried access tokens, for server-rendered pages.
45
+ *
46
+ * The token system assumes an API client that can hold a bearer token and put
47
+ * it in a header. A server-rendered page has no such client: the browser posts
48
+ * a form, follows a redirect, and comes back with nothing but cookies. So
49
+ * those apps ended up either inventing their own cookie format or reaching for
50
+ * `SessionAuth`, whose in-memory map drops every session when the process
51
+ * restarts and cannot be shared across workers.
52
+ *
53
+ * This carries the same personal access token the API uses in an httpOnly
54
+ * cookie: one source of truth for who is signed in, revocable through the
55
+ * usual token calls, and durable because the token lives in the database.
56
+ *
57
+ * The cookie is httpOnly (a page script never needs it), SameSite=Lax (so a
58
+ * link from an email still arrives signed in, while a cross-site POST does
59
+ * not), and Secure everywhere except local HTTP development.
60
+ */
61
+ export declare interface AuthCookieOptions {
62
+ name?: string
63
+ maxAge?: number
64
+ path?: string
65
+ domain?: string
66
+ secure?: boolean
67
+ sameSite?: 'Strict' | 'Lax' | 'None'
68
+ }
69
+ /** Whatever the token layer resolves a user to, so the two cannot drift. */
70
+ declare type AuthenticatedUser = Awaited<ReturnType<typeof Auth.getUserFromToken>>;
@@ -0,0 +1,63 @@
1
+ import { config } from "@stacksjs/config";
2
+ import { Auth } from "./authentication";
3
+ function cookieName(options) {
4
+ return options?.name ?? config.auth?.cookie?.name ?? "stacks_auth";
5
+ }
6
+ function defaultMaxAge() {
7
+ const days = Number(config.auth?.tokenExpiry ?? 30);
8
+ return Math.max(60, Math.round(days * 24 * 60 * 60));
9
+ }
10
+ function isLocal() {
11
+ const environment = String(config.app?.env ?? process.env.APP_ENV ?? "");
12
+ return environment === "local" || environment === "development" || environment === "dev";
13
+ }
14
+ export function authCookie(token, options = {}) {
15
+ const parts = [
16
+ `${cookieName(options)}=${encodeURIComponent(token)}`,
17
+ `Path=${options.path ?? "/"}`,
18
+ `Max-Age=${options.maxAge ?? defaultMaxAge()}`,
19
+ "HttpOnly",
20
+ `SameSite=${options.sameSite ?? "Lax"}`
21
+ ];
22
+ if (options.domain)
23
+ parts.push(`Domain=${options.domain}`);
24
+ if (options.secure ?? !isLocal())
25
+ parts.push("Secure");
26
+ return parts.join("; ");
27
+ }
28
+ export function clearAuthCookie(options = {}) {
29
+ return authCookie("", { ...options, maxAge: 0 });
30
+ }
31
+ export function authCookieToken(request, options = {}) {
32
+ const header = request.headers.get("cookie");
33
+ if (!header)
34
+ return;
35
+ const wanted = cookieName(options);
36
+ for (const pair of header.split(";")) {
37
+ const index = pair.indexOf("=");
38
+ if (index === -1)
39
+ continue;
40
+ if (pair.slice(0, index).trim() !== wanted)
41
+ continue;
42
+ const value = decodeURIComponent(pair.slice(index + 1).trim());
43
+ return value.length > 0 ? value : void 0;
44
+ }
45
+ return;
46
+ }
47
+ export async function userFromCookie(request, options = {}) {
48
+ const token = authCookieToken(request, options);
49
+ if (!token)
50
+ return;
51
+ return Auth.getUserFromToken(token);
52
+ }
53
+ export async function cookieCheck(request, options = {}) {
54
+ return Boolean(await userFromCookie(request, options));
55
+ }
56
+ export async function logoutCookie(request, options = {}) {
57
+ const token = authCookieToken(request, options);
58
+ if (token)
59
+ try {
60
+ await Auth.revokeToken(token);
61
+ } catch {}
62
+ return clearAuthCookie(options);
63
+ }
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export { DEFAULT_ROLE_PACKS, seedDefaultRoles } from './rbac-seed';
24
24
  export * from './email-verification';
25
25
  // Session-based Authentication (SPA Cookie Auth)
26
26
  export * from './session-auth';
27
+ // Cookie-carried access tokens, for server-rendered pages.
28
+ export * from './cookie-auth';
27
29
  // TOTP (Two-Factor Authentication) - re-export from ts-auth
28
30
  export {
29
31
  generateTOTP,
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ export { createBqbRbacStore } from "./rbac-store-bqb";
17
17
  export { DEFAULT_ROLE_PACKS, seedDefaultRoles } from "./rbac-seed";
18
18
  export * from "./email-verification";
19
19
  export * from "./session-auth";
20
+ export * from "./cookie-auth";
20
21
  export {
21
22
  generateTOTP,
22
23
  verifyTOTP,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/auth",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.177",
5
+ "version": "0.70.179",
6
6
  "description": "A more simplistic way to authenticate.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "better-dx": "^0.2.17",
59
- "@stacksjs/error-handling": "0.70.177",
60
- "@stacksjs/router": "0.70.177"
59
+ "@stacksjs/error-handling": "0.70.179",
60
+ "@stacksjs/router": "0.70.179"
61
61
  }
62
62
  }