@stacksjs/auth 0.70.178 → 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.
- package/dist/authentication.js +4 -2
- package/dist/cookie-auth.d.ts +70 -0
- package/dist/cookie-auth.js +63 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +3 -3
package/dist/authentication.js
CHANGED
|
@@ -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
|
-
|
|
145
|
-
|
|
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.
|
|
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.
|
|
60
|
-
"@stacksjs/router": "0.70.
|
|
59
|
+
"@stacksjs/error-handling": "0.70.179",
|
|
60
|
+
"@stacksjs/router": "0.70.179"
|
|
61
61
|
}
|
|
62
62
|
}
|