@warlock.js/auth 4.9.1 → 4.10.0
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/CHANGELOG.md +6 -0
- package/llms-full.txt +210 -173
- package/llms.txt +18 -18
- package/package.json +7 -7
- package/skills/protect-routes/SKILL.md +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/auth` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 4.10.0
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `protect-routes` documents two things that were previously only discoverable by reading source: that `authMiddleware` gates on **user type by flat string match** and cannot express a permission matrix, role hierarchy, or who-may-act-on-whom — with a worked pointer to `@warlock.js/access` (`gate`, `can`, `definePolicy`) for exactly that; and that auth is **bearer-token by design**, with cookie sessions and CSRF being app-level work rather than an omission
|
|
12
|
+
|
|
7
13
|
## 4.2.11
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/llms-full.txt
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Warlock Auth — full skills
|
|
2
|
-
|
|
3
|
-
> Package: `@warlock.js/auth`
|
|
4
|
-
|
|
5
|
-
> Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/auth/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
|
|
6
|
-
|
|
7
|
-
## auth-basics `@warlock.js/auth/auth-basics/SKILL.md`
|
|
8
|
-
|
|
1
|
+
# Warlock Auth — full skills
|
|
2
|
+
|
|
3
|
+
> Package: `@warlock.js/auth`
|
|
4
|
+
|
|
5
|
+
> Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/auth/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
|
|
6
|
+
|
|
7
|
+
## auth-basics `@warlock.js/auth/auth-basics/SKILL.md`
|
|
8
|
+
|
|
9
9
|
---
|
|
10
10
|
name: auth-basics
|
|
11
11
|
description: 'Start with @warlock.js/auth — JWT auth, Auth base model, authMiddleware route gate, authService (login / logout / refresh), AccessToken + RefreshToken persistence, multi-user-type support. Triggers: `Auth`, `authMiddleware`, `authService`, `AccessToken`, `RefreshToken`, `authMigrations`; "set up auth in a new app", "which auth skill do I need", "JWT authentication overview", "wire warlock auth"; typical import `import { authMiddleware, authService, Auth, authMigrations } from "@warlock.js/auth"`. Skip: routing — `@warlock.js/auth/protect-routes/SKILL.md`; login — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; competing libs `passport`, `next-auth`, `lucia-auth`, `auth0`.'
|
|
@@ -95,10 +95,10 @@ export default {
|
|
|
95
95
|
- Don't store the JWT secret in the model layer or anywhere user-modifiable. It lives in `.env` only.
|
|
96
96
|
- Don't return the raw `User` from a login endpoint without shaping output. Configure `static toJsonColumns` or `static resource` (see [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md)).
|
|
97
97
|
- Don't run `auth.cleanup` from app boot. Schedule it (cron, scheduler) as a periodic task — see [`@warlock.js/scheduler/scheduler-basics/SKILL.md`](@warlock.js/scheduler/scheduler-basics/SKILL.md).
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## customize-token-storage `@warlock.js/auth/customize-token-storage/SKILL.md`
|
|
101
|
-
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## customize-token-storage `@warlock.js/auth/customize-token-storage/SKILL.md`
|
|
101
|
+
|
|
102
102
|
---
|
|
103
103
|
name: customize-token-storage
|
|
104
104
|
description: 'Override the persisted AccessToken / RefreshToken models to add columns (multi-tenant `organization_id`, device metadata), rename, or change storage — without forking the package. Extend the model + `schema.extend(...)`, register it under `config.auth.accessToken.model` / `config.auth.refreshToken.model`, override `issue()` to populate the new column, and add a migration. Triggers: `accessToken.model`, `refreshToken.model`, `AccessToken.issue`, `RefreshToken.issue`, `accessTokenSchema`, `refreshTokenSchema`, "add a column to the token table", "multi-tenant tokens", "organization_id on access token", "override the token model", "custom token storage"; typical import `import { AccessToken, accessTokenSchema } from "@warlock.js/auth"`. Skip: multiple user TYPES (not token storage) — `@warlock.js/auth/customize-user-type/SKILL.md`; the token lifecycle API — `@warlock.js/auth/manage-tokens/SKILL.md`; the config blocks themselves — `@warlock.js/auth/auth-basics/SKILL.md`.'
|
|
@@ -211,10 +211,10 @@ If you rename a column, override the statics that reference it so they map to yo
|
|
|
211
211
|
- [`@warlock.js/auth/customize-user-type/SKILL.md`](@warlock.js/auth/customize-user-type/SKILL.md) — multiple user *types* (a different axis from token *storage*).
|
|
212
212
|
- [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md) — the token lifecycle your statics power.
|
|
213
213
|
- [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md) — extending models and schemas.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
## customize-user-type `@warlock.js/auth/customize-user-type/SKILL.md`
|
|
217
|
-
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## customize-user-type `@warlock.js/auth/customize-user-type/SKILL.md`
|
|
217
|
+
|
|
218
218
|
---
|
|
219
219
|
name: customize-user-type
|
|
220
220
|
description: 'Support multiple user types (user / admin / client / staff) in one auth system — each Auth subclass overrides userType, config.auth.userType.<slug> maps slug to model class, authMiddleware(''admin'') gates per type. Triggers: `Auth`, `userType`, `config.auth.userType`, `Authenticable`, `@RegisterModel`, `confirmPassword`; "add admins and users", "multiple user types", "separate client and vendor personas", "per-type login"; typical import `import { Auth } from "@warlock.js/auth"`. Skip: `authMiddleware` semantics — `@warlock.js/auth/protect-routes/SKILL.md`; login flow — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; RBAC libs `casl`, `accesscontrol`, `rbac`.'
|
|
@@ -357,10 +357,10 @@ abstract class Auth<TSchema> extends Model<TSchema> implements Authenticable {
|
|
|
357
357
|
- [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md) — `authMiddleware` semantics
|
|
358
358
|
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — passing the right Model to `login`
|
|
359
359
|
- [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md) — `@RegisterModel`, models in general
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
## handle-login-and-logout `@warlock.js/auth/handle-login-and-logout/SKILL.md`
|
|
363
|
-
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
## handle-login-and-logout `@warlock.js/auth/handle-login-and-logout/SKILL.md`
|
|
363
|
+
|
|
364
364
|
---
|
|
365
365
|
name: handle-login-and-logout
|
|
366
366
|
description: 'Run the full login flow via authService.login(Model, credentials, deviceInfo?) — verify password, create access + refresh token pair, fire events. Logout via authService.logout(user, accessToken?, refreshToken?) revokes tokens. Triggers: `authService.login`, `authService.logout`, `authService.attemptLogin`, `authService.refreshTokens`, `authService.revokeAllTokens`, `authEvents`; "build a login endpoint", "POST /login controller", "logout from all devices", "verify credentials and issue tokens"; typical import `import { authService, authEvents } from "@warlock.js/auth"`. Skip: token internals — `@warlock.js/auth/manage-tokens/SKILL.md`; sign-up — `@warlock.js/auth/register-user/SKILL.md`; competing libs `passport-local`, `next-auth` credentials.'
|
|
@@ -521,10 +521,10 @@ Full event list: `login.attempt`, `login.success`, `login.failed`, `logout`, `lo
|
|
|
521
521
|
- [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md) — token lifecycle, rotation, family revocation
|
|
522
522
|
- [`@warlock.js/auth/register-user/SKILL.md`](@warlock.js/auth/register-user/SKILL.md) — sign-up that issues tokens after creation
|
|
523
523
|
- [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md) — where the access token gets consumed
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
## manage-tokens `@warlock.js/auth/manage-tokens/SKILL.md`
|
|
527
|
-
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
## manage-tokens `@warlock.js/auth/manage-tokens/SKILL.md`
|
|
527
|
+
|
|
528
528
|
---
|
|
529
529
|
name: manage-tokens
|
|
530
530
|
description: 'Token lifecycle — generateAccessToken, createRefreshToken, createTokenPair, refreshTokens (with rotation + replay detection), revokeAllTokens, revokeTokenFamily, cleanupExpiredTokens, getActiveSessions. Triggers: `createTokenPair`, `refreshTokens`, `revokeTokenFamily`, `cleanupExpiredTokens`, `getActiveSessions`, `jwt.generate`, `jwt.verify`, `AccessToken`, `RefreshToken`; "rotate refresh tokens", "detect token replay", "logout from all devices", "list active sessions", "clean up expired tokens"; typical import `import { authService, jwt } from "@warlock.js/auth"`. Skip: login flow — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; CLI cleanup — `@warlock.js/auth/run-auth-commands/SKILL.md`; competing libs `jsonwebtoken`, `jose`, `fast-jwt`.'
|
|
@@ -694,10 +694,10 @@ The package signs access and refresh tokens with independent secrets — `config
|
|
|
694
694
|
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — full login/logout flow that uses these primitives
|
|
695
695
|
- [`@warlock.js/auth/run-auth-commands/SKILL.md`](@warlock.js/auth/run-auth-commands/SKILL.md) — the bundled cleanup command
|
|
696
696
|
- [`@warlock.js/scheduler/scheduler-basics/SKILL.md`](@warlock.js/scheduler/scheduler-basics/SKILL.md) — scheduling cleanup
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
## overview `@warlock.js/auth/overview/SKILL.md`
|
|
700
|
-
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
## overview `@warlock.js/auth/overview/SKILL.md`
|
|
700
|
+
|
|
701
701
|
---
|
|
702
702
|
name: overview
|
|
703
703
|
description: 'Front-door orientation for `@warlock.js/auth` — JWT authentication for Warlock apps: the `Auth` base model, `authMiddleware` route gate, `authService` (login / logout / refresh with token rotation + replay detection), persisted AccessToken + RefreshToken, multi-user-type support, auth lifecycle events, and two CLI commands. Coupled to `@warlock.js/core`. TRIGGER when: code imports anything from `@warlock.js/auth`; user asks "what does @warlock.js/auth do", "how do I add login to my Warlock app", "JWT auth in Warlock", "protect a route", "multiple user types / admin + user", "refresh token rotation"; package.json adds `@warlock.js/auth`. Skip: specific task already known — load the matching task skill directly (`auth-basics`, `protect-routes`, `handle-login-and-logout`, `register-user`, `manage-tokens`, `customize-user-type`, `run-auth-commands`); non-Warlock apps (this package depends on core); session-cookie auth (this is JWT/token-based).'
|
|
@@ -770,10 +770,10 @@ Two CLI commands — `warlock jwt.generate` (strong JWT secret → `.env`) and `
|
|
|
770
770
|
- [`@warlock.js/core/warlock-conventions/SKILL.md`](@warlock.js/core/warlock-conventions/SKILL.md) — the framework auth runs inside (routing, middleware, config).
|
|
771
771
|
- [`@warlock.js/cascade/cascade-basics/SKILL.md`](@warlock.js/cascade/cascade-basics/SKILL.md) — the ORM behind the `Auth`, `AccessToken`, and `RefreshToken` models.
|
|
772
772
|
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this `overview/SKILL.md` becomes `.claude/skills/warlock-js-auth-overview/`.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
## protect-routes `@warlock.js/auth/protect-routes/SKILL.md`
|
|
776
|
-
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
## protect-routes `@warlock.js/auth/protect-routes/SKILL.md`
|
|
776
|
+
|
|
777
777
|
---
|
|
778
778
|
name: protect-routes
|
|
779
779
|
description: 'Gate HTTP routes via authMiddleware(allowedUserType) — the argument is required and a valid token is always required: [] allows any authenticated user, a user-type restricts to those types. Sets request.user + request.decodedAccessToken on success, 401 on failure. Triggers: `authMiddleware`, `request.user`, `request.decodedAccessToken`, `AuthErrorCodes`, `MissingAccessToken`, `InvalidAccessToken`; "how do I protect a route", "restrict route by user type", "require any logged-in user"; typical import `import { authMiddleware } from "@warlock.js/auth"`. Skip: multi-user-type config — `@warlock.js/auth/customize-user-type/SKILL.md`; issuing the token — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; competing libs `passport`, `express-jwt`, `next-auth` middleware.'
|
|
@@ -868,161 +868,198 @@ async function feedController(request: Request, response: Response) {
|
|
|
868
868
|
|
|
869
869
|
The middleware uses the framework's `response.unauthorized({...})` shape. To override the response globally, hook the framework's error transformer to remap `AuthErrorCodes.*` codes.
|
|
870
870
|
|
|
871
|
-
##
|
|
872
|
-
|
|
873
|
-
- Don't call `authMiddleware` outside route definition. It returns a function — the function is what runs per-request. Calling it once per request creates a fresh middleware on every hit (wasteful) and a fresh allowed-types Set (correctness if the input changes per call).
|
|
874
|
-
- Don't manually decode JWTs in the controller. The middleware already does it and exposes the decoded payload via `request.decodedAccessToken`.
|
|
875
|
-
- Don't trust `request.user` set by client-supplied headers. The middleware is the only place that sets it on the server — client headers can't reach this slot.
|
|
876
|
-
- Don't pass an unknown user-type to `authMiddleware("typo")`. The middleware will reject every request because the lookup fails. Test the wire-up with a real token of each user type.
|
|
877
|
-
|
|
878
|
-
## See also
|
|
879
|
-
|
|
880
|
-
- [`@warlock.js/auth/customize-user-type/SKILL.md`](@warlock.js/auth/customize-user-type/SKILL.md) — config and multi-user-type semantics
|
|
881
|
-
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — where the access token gets issued in the first place
|
|
882
|
-
- [`@warlock.js/auth/throttle-login-attempts/SKILL.md`](@warlock.js/auth/throttle-login-attempts/SKILL.md) — brute-force throttle to stack in front of the login route
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
## register-user `@warlock.js/auth/register-user/SKILL.md`
|
|
886
|
-
|
|
887
|
-
---
|
|
888
|
-
name: register-user
|
|
889
|
-
description: 'Sign up a new user and issue the initial token pair — User.create({...password: await hashPassword(plain)}) then authService.createTokenPair(user). Triggers: `User.create`, `hashPassword`, `verifyPassword`, `authService.createTokenPair`, `toJsonColumns`, `strongPassword`, `authEvents`; "build a register endpoint", "POST /register controller", "sign up a new user", "hash password on signup", "email verification flow"; typical import `import { authService } from "@warlock.js/auth"; import { hashPassword } from "@warlock.js/core"`. Skip: login — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; token internals — `@warlock.js/auth/manage-tokens/SKILL.md`; competing libs `bcrypt`, `bcryptjs`, `argon2`.'
|
|
890
|
-
---
|
|
891
|
-
|
|
892
|
-
# Register-and-issue-tokens flow
|
|
893
|
-
|
|
894
|
-
Two-step on the server: create the user (with hashed password), then issue tokens. Cascade handles the persistence; `authService` handles the tokens.
|
|
895
|
-
|
|
896
|
-
## The minimal shape
|
|
897
|
-
|
|
898
|
-
```ts
|
|
899
|
-
import { authService } from "@warlock.js/auth";
|
|
900
|
-
import { hashPassword } from "@warlock.js/core";
|
|
901
|
-
import { User } from "@/app/users/models/user.model";
|
|
902
|
-
|
|
903
|
-
async function registerController(request: Request, response: Response) {
|
|
904
|
-
const { email, password, name } = request.all();
|
|
905
|
-
|
|
906
|
-
// 1. Check duplicates
|
|
907
|
-
const existing = await User.first({ email });
|
|
908
|
-
if (existing) {
|
|
909
|
-
return response.conflict({ error: "Email already registered" });
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// 2. Create the user with hashed password
|
|
913
|
-
const user = await User.create({
|
|
914
|
-
email,
|
|
915
|
-
name,
|
|
916
|
-
password: await hashPassword(password),
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
// 3. Issue tokens
|
|
920
|
-
const tokens = await authService.createTokenPair(user, {
|
|
921
|
-
userAgent: request.header("user-agent"),
|
|
922
|
-
ip: request.ip,
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
// 4. Respond
|
|
926
|
-
return response.successCreate({
|
|
927
|
-
user, // shape via static toJsonColumns / static resource
|
|
928
|
-
tokens,
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
```
|
|
932
|
-
|
|
933
|
-
That's the whole flow. `User.create({...})` runs the schema validation (including `.email()`, `.min()`, etc. on each field), so you don't need a separate validation pass — see [`@warlock.js/seal/handle-seal-errors/SKILL.md`](@warlock.js/seal/handle-seal-errors/SKILL.md) for catching validation failures.
|
|
934
|
-
|
|
935
|
-
## Hash the password on the way in
|
|
936
|
-
|
|
937
|
-
Always pass `hashPassword(plain)` — never store the plain password. The `hashPassword` helper is `bcrypt`-based and async; the cost factor matches the framework default.
|
|
938
|
-
|
|
939
|
-
```ts
|
|
940
|
-
import { hashPassword, verifyPassword } from "@warlock.js/core";
|
|
941
|
-
|
|
942
|
-
const hash = await hashPassword("plaintext"); // store this
|
|
943
|
-
const ok = await verifyPassword("plaintext", hash); // compare on login
|
|
944
|
-
```
|
|
871
|
+
## When flat user types aren't enough — reach for `@warlock.js/access`
|
|
945
872
|
|
|
946
|
-
`
|
|
873
|
+
`authMiddleware` gates on **user type** by flat string membership. That answers "is this an admin?" and nothing more. It deliberately cannot express:
|
|
947
874
|
|
|
948
|
-
|
|
875
|
+
- a permission matrix (`articles.publish` rather than `"editor"`)
|
|
876
|
+
- role hierarchy or inheritance
|
|
877
|
+
- **who may act on whom** — "an `admin` may create a `teacher`, but only a `superAdmin` may create or promote an `admin`"
|
|
949
878
|
|
|
950
|
-
|
|
879
|
+
That last one is the difference between a role check and an authorization model: without it, an account-creation endpoint gated on `authMiddleware("admin")` is a privilege-escalation path.
|
|
951
880
|
|
|
952
|
-
|
|
953
|
-
const userSchema = v.object({
|
|
954
|
-
email: v.string().email(),
|
|
955
|
-
name: v.string().min(2).max(120),
|
|
956
|
-
password: v.string().strongPassword(12), // 12+ chars, upper/lower/digit/symbol
|
|
957
|
-
// status, role, etc.
|
|
958
|
-
});
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
But **don't return the password in the public output**:
|
|
881
|
+
Install [`@warlock.js/access`](@warlock.js/access/overview/SKILL.md) for that. It layers RBAC plus per-permission ABAC policies over the same authenticated user:
|
|
962
882
|
|
|
963
883
|
```ts
|
|
964
|
-
@
|
|
965
|
-
export class User extends Model<UserSchema> {
|
|
966
|
-
public static table = "users";
|
|
967
|
-
public static schema = userSchema;
|
|
968
|
-
public static toJsonColumns = ["id", "email", "name", "created_at"]; // omit password
|
|
969
|
-
}
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
Without this, `JSON.stringify(user)` in your response leaks the hash.
|
|
973
|
-
|
|
974
|
-
## Email verification flow (extending registration)
|
|
884
|
+
import { gate, can, definePolicy } from "@warlock.js/access";
|
|
975
885
|
|
|
976
|
-
|
|
886
|
+
// Permission-based route gate, in place of a user-type gate
|
|
887
|
+
router.post("/articles", articlesController, { middleware: [authMiddleware([]), gate("articles.create")] });
|
|
977
888
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
verification_token: Random.string(64),
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
await mailer.sendVerificationEmail(user.get("email"), user.get("verification_token"));
|
|
889
|
+
// Who-may-act-on-whom: an ABAC policy on top of the RBAC grant
|
|
890
|
+
definePolicy("users.create", (actor, target, ctx) =>
|
|
891
|
+
ctx.hasRole("superAdmin") || (target as User).userType === "teacher",
|
|
892
|
+
);
|
|
986
893
|
|
|
987
|
-
|
|
988
|
-
return response.successCreate({ user, tokens });
|
|
894
|
+
if (await can(request.user, "users.create", { resource: payload })) { /* ... */ }
|
|
989
895
|
```
|
|
990
896
|
|
|
991
|
-
|
|
897
|
+
Use `authMiddleware` to establish *who the caller is*, and `access` to decide *what they may do*. They compose — `access` reads the user `authMiddleware` put on the request.
|
|
992
898
|
|
|
993
|
-
##
|
|
899
|
+
## Sessions are bearer-token, not cookie
|
|
994
900
|
|
|
995
|
-
|
|
901
|
+
The token is read from the `Authorization` header (`Bearer <token>` or `Key <token>`) and nowhere else. "Session" in this package means a **refresh-token row**, not an HTTP cookie session.
|
|
996
902
|
|
|
997
|
-
|
|
998
|
-
import { authEvents } from "@warlock.js/auth";
|
|
903
|
+
There is no cookie-backed guard, no session store, and no cookie strategy in `authMiddleware` — that is the intended design, not an omission. Warlock's auth optimises for API services.
|
|
999
904
|
|
|
1000
|
-
|
|
1001
|
-
if (user.get("created_at") > new Date(Date.now() - 5000)) {
|
|
1002
|
-
// freshly created in the last 5s — treat as registration
|
|
1003
|
-
await sendWelcomeEmail(user);
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
```
|
|
1007
|
-
|
|
1008
|
-
Cleaner alternative: emit your own `user.registered` event from the controller after `User.create`. Decouples auth-package events from your domain events.
|
|
905
|
+
If you want HttpOnly cookie sessions for a server-rendered admin, that is **app-level work**: set the cookie yourself with `response.cookie` (secure by default since 4.10.0) and read it in your own middleware, then hand the token to the same `authService` rotation logic. **You also own CSRF** — the framework ships no CSRF helper, and cookie auth needs one where bearer auth does not. A synchronizer token bound to the session plus `Origin`/`Referer` checks is the usual shape.
|
|
1009
906
|
|
|
1010
907
|
## Things NOT to do
|
|
1011
908
|
|
|
1012
|
-
- Don't
|
|
1013
|
-
- Don't
|
|
1014
|
-
- Don't
|
|
1015
|
-
- Don't
|
|
909
|
+
- Don't call `authMiddleware` outside route definition. It returns a function — the function is what runs per-request. Calling it once per request creates a fresh middleware on every hit (wasteful) and a fresh allowed-types Set (correctness if the input changes per call).
|
|
910
|
+
- Don't manually decode JWTs in the controller. The middleware already does it and exposes the decoded payload via `request.decodedAccessToken`.
|
|
911
|
+
- Don't trust `request.user` set by client-supplied headers. The middleware is the only place that sets it on the server — client headers can't reach this slot.
|
|
912
|
+
- Don't pass an unknown user-type to `authMiddleware("typo")`. The middleware will reject every request because the lookup fails. Test the wire-up with a real token of each user type.
|
|
1016
913
|
|
|
1017
914
|
## See also
|
|
1018
915
|
|
|
1019
|
-
- [`@warlock.js/auth/
|
|
1020
|
-
- [`@warlock.js/auth/
|
|
1021
|
-
- [`@warlock.js/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
916
|
+
- [`@warlock.js/auth/customize-user-type/SKILL.md`](@warlock.js/auth/customize-user-type/SKILL.md) — config and multi-user-type semantics
|
|
917
|
+
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — where the access token gets issued in the first place
|
|
918
|
+
- [`@warlock.js/auth/throttle-login-attempts/SKILL.md`](@warlock.js/auth/throttle-login-attempts/SKILL.md) — brute-force throttle to stack in front of the login route
|
|
919
|
+
- [`@warlock.js/access/overview/SKILL.md`](@warlock.js/access/overview/SKILL.md) — permissions, roles, and who-may-act-on-whom policies, when user-type gating isn't enough
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
## register-user `@warlock.js/auth/register-user/SKILL.md`
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
name: register-user
|
|
926
|
+
description: 'Sign up a new user and issue the initial token pair — User.create({...password: await hashPassword(plain)}) then authService.createTokenPair(user). Triggers: `User.create`, `hashPassword`, `verifyPassword`, `authService.createTokenPair`, `toJsonColumns`, `strongPassword`, `authEvents`; "build a register endpoint", "POST /register controller", "sign up a new user", "hash password on signup", "email verification flow"; typical import `import { authService } from "@warlock.js/auth"; import { hashPassword } from "@warlock.js/core"`. Skip: login — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; token internals — `@warlock.js/auth/manage-tokens/SKILL.md`; competing libs `bcrypt`, `bcryptjs`, `argon2`.'
|
|
927
|
+
---
|
|
928
|
+
|
|
929
|
+
# Register-and-issue-tokens flow
|
|
930
|
+
|
|
931
|
+
Two-step on the server: create the user (with hashed password), then issue tokens. Cascade handles the persistence; `authService` handles the tokens.
|
|
932
|
+
|
|
933
|
+
## The minimal shape
|
|
934
|
+
|
|
935
|
+
```ts
|
|
936
|
+
import { authService } from "@warlock.js/auth";
|
|
937
|
+
import { hashPassword } from "@warlock.js/core";
|
|
938
|
+
import { User } from "@/app/users/models/user.model";
|
|
939
|
+
|
|
940
|
+
async function registerController(request: Request, response: Response) {
|
|
941
|
+
const { email, password, name } = request.all();
|
|
942
|
+
|
|
943
|
+
// 1. Check duplicates
|
|
944
|
+
const existing = await User.first({ email });
|
|
945
|
+
if (existing) {
|
|
946
|
+
return response.conflict({ error: "Email already registered" });
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// 2. Create the user with hashed password
|
|
950
|
+
const user = await User.create({
|
|
951
|
+
email,
|
|
952
|
+
name,
|
|
953
|
+
password: await hashPassword(password),
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// 3. Issue tokens
|
|
957
|
+
const tokens = await authService.createTokenPair(user, {
|
|
958
|
+
userAgent: request.header("user-agent"),
|
|
959
|
+
ip: request.ip,
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// 4. Respond
|
|
963
|
+
return response.successCreate({
|
|
964
|
+
user, // shape via static toJsonColumns / static resource
|
|
965
|
+
tokens,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
That's the whole flow. `User.create({...})` runs the schema validation (including `.email()`, `.min()`, etc. on each field), so you don't need a separate validation pass — see [`@warlock.js/seal/handle-seal-errors/SKILL.md`](@warlock.js/seal/handle-seal-errors/SKILL.md) for catching validation failures.
|
|
971
|
+
|
|
972
|
+
## Hash the password on the way in
|
|
973
|
+
|
|
974
|
+
Always pass `hashPassword(plain)` — never store the plain password. The `hashPassword` helper is `bcrypt`-based and async; the cost factor matches the framework default.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
import { hashPassword, verifyPassword } from "@warlock.js/core";
|
|
978
|
+
|
|
979
|
+
const hash = await hashPassword("plaintext"); // store this
|
|
980
|
+
const ok = await verifyPassword("plaintext", hash); // compare on login
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
`authService.attemptLogin` already calls `verifyPassword` against the stored hash — you don't compare passwords manually.
|
|
984
|
+
|
|
985
|
+
## Schema enforcement
|
|
986
|
+
|
|
987
|
+
Define the password as `v.string().strongPassword(12)` (or similar) in your User schema so weak passwords are rejected at `create()` time:
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
const userSchema = v.object({
|
|
991
|
+
email: v.string().email(),
|
|
992
|
+
name: v.string().min(2).max(120),
|
|
993
|
+
password: v.string().strongPassword(12), // 12+ chars, upper/lower/digit/symbol
|
|
994
|
+
// status, role, etc.
|
|
995
|
+
});
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
But **don't return the password in the public output**:
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
@RegisterModel()
|
|
1002
|
+
export class User extends Model<UserSchema> {
|
|
1003
|
+
public static table = "users";
|
|
1004
|
+
public static schema = userSchema;
|
|
1005
|
+
public static toJsonColumns = ["id", "email", "name", "created_at"]; // omit password
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
Without this, `JSON.stringify(user)` in your response leaks the hash.
|
|
1010
|
+
|
|
1011
|
+
## Email verification flow (extending registration)
|
|
1012
|
+
|
|
1013
|
+
Common pattern: create the user as `email_verified = false`, send a verification email, mark verified on click. The auth package doesn't ship this; build it on top:
|
|
1014
|
+
|
|
1015
|
+
```ts
|
|
1016
|
+
const user = await User.create({
|
|
1017
|
+
...data,
|
|
1018
|
+
email_verified: false,
|
|
1019
|
+
verification_token: Random.string(64),
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
await mailer.sendVerificationEmail(user.get("email"), user.get("verification_token"));
|
|
1023
|
+
|
|
1024
|
+
const tokens = await authService.createTokenPair(user);
|
|
1025
|
+
return response.successCreate({ user, tokens });
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
Optional: pre-verification, restrict the user to a `unverified` user-type and gate routes accordingly via `authMiddleware("user")`. After verification, swap user-type to `user`.
|
|
1029
|
+
|
|
1030
|
+
## Side effects via auth events
|
|
1031
|
+
|
|
1032
|
+
Hook post-registration logic:
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
import { authEvents } from "@warlock.js/auth";
|
|
1036
|
+
|
|
1037
|
+
authEvents.on("session.created", async (user, refreshToken, deviceInfo) => {
|
|
1038
|
+
if (user.get("created_at") > new Date(Date.now() - 5000)) {
|
|
1039
|
+
// freshly created in the last 5s — treat as registration
|
|
1040
|
+
await sendWelcomeEmail(user);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
Cleaner alternative: emit your own `user.registered` event from the controller after `User.create`. Decouples auth-package events from your domain events.
|
|
1046
|
+
|
|
1047
|
+
## Things NOT to do
|
|
1048
|
+
|
|
1049
|
+
- Don't pass the plain password to `User.create()`. `await hashPassword(plain)` first.
|
|
1050
|
+
- Don't return the user without `toJsonColumns` / `resource` shaping — the password hash will leak otherwise.
|
|
1051
|
+
- Don't issue tokens before validating the user shape. `User.create` runs validation; let it throw on bad input before tokens get created.
|
|
1052
|
+
- Don't run "send welcome email" inline in the controller. Push it to a queue or run it after-commit via the outbox pattern — see [`@warlock.js/cascade/manage-transactions/SKILL.md`](@warlock.js/cascade/manage-transactions/SKILL.md).
|
|
1053
|
+
|
|
1054
|
+
## See also
|
|
1055
|
+
|
|
1056
|
+
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — login flow (same `createTokenPair` step)
|
|
1057
|
+
- [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md) — token issuance internals
|
|
1058
|
+
- [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md) — `toJsonColumns` / `resource` for public output
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
## run-auth-commands `@warlock.js/auth/run-auth-commands/SKILL.md`
|
|
1062
|
+
|
|
1026
1063
|
---
|
|
1027
1064
|
name: run-auth-commands
|
|
1028
1065
|
description: 'Two bundled CLI commands — warlock jwt.generate (creates strong JWT secret + writes to .env) and warlock auth.cleanup (removes expired refresh tokens). Register via registerJWTSecretGeneratorCommand() and registerAuthCleanupCommand(). Triggers: `registerJWTSecretGeneratorCommand`, `registerAuthCleanupCommand`, `warlock jwt.generate`, `warlock auth.cleanup`, `cleanupExpiredTokens`, `command`; "generate JWT secret", "bootstrap .env JWT_SECRET", "cron job for expired tokens", "schedule auth cleanup"; typical import `import { registerJWTSecretGeneratorCommand, registerAuthCleanupCommand } from "@warlock.js/auth"`. Skip: programmatic cleanup — `@warlock.js/auth/manage-tokens/SKILL.md`; in-process scheduling — `@warlock.js/scheduler/scheduler-basics/SKILL.md`; competing tools `dotenv-cli`, `node-cron`.'
|
|
@@ -1069,7 +1106,7 @@ Run it once when setting up a new project. Each developer typically runs it loca
|
|
|
1069
1106
|
yarn warlock auth.cleanup
|
|
1070
1107
|
```
|
|
1071
1108
|
|
|
1072
|
-
Runs `authService.cleanupExpiredTokens()` — deletes every refresh token whose `expires_at` has passed. Fires `token.expired` per token and `cleanup.completed` once.
|
|
1109
|
+
Runs `authService.cleanupExpiredTokens()` — deletes every refresh token whose `expires_at` has passed, then sweeps expired access-token rows too. Fires `token.expired` per refresh token and `cleanup.completed` once.
|
|
1073
1110
|
|
|
1074
1111
|
Schedule it periodically. Two common shapes:
|
|
1075
1112
|
|
|
@@ -1148,10 +1185,10 @@ Register it the same way as the bundled commands — call the factory inside `de
|
|
|
1148
1185
|
|
|
1149
1186
|
- [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md) — `cleanupExpiredTokens` internals
|
|
1150
1187
|
- [`@warlock.js/scheduler/scheduler-basics/SKILL.md`](@warlock.js/scheduler/scheduler-basics/SKILL.md) — in-process scheduling
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
## throttle-login-attempts `@warlock.js/auth/throttle-login-attempts/SKILL.md`
|
|
1154
|
-
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
## throttle-login-attempts `@warlock.js/auth/throttle-login-attempts/SKILL.md`
|
|
1191
|
+
|
|
1155
1192
|
---
|
|
1156
1193
|
name: throttle-login-attempts
|
|
1157
1194
|
description: 'Brute-force / credential-stuffing protection via `loginThrottleMiddleware` — a failure-aware route gate that counts only failed logins (resets on success), locks per-account and per-source after a threshold, and rejects pre-controller with 429 so the DB lookup and bcrypt verify are skipped. Cache-backed (shared across replicas), fixed-window, fails open on a cache outage. Triggers: `loginThrottleMiddleware`, `AuthErrorCodes.TooManyAttempts`, `EC004`, "rate limit login", "brute force protection", "lock account after failed logins", "throttle login attempts", "too many login attempts 429"; typical import `import { loginThrottleMiddleware } from "@warlock.js/auth"`. Skip: generic per-route request rate limiting that counts every request (use core `middleware.rateLimit`); gating a route by auth — `@warlock.js/auth/protect-routes/SKILL.md`; issuing tokens — `@warlock.js/auth/handle-login-and-logout/SKILL.md`.'
|
|
@@ -1268,5 +1305,5 @@ They compose. For a hard ceiling on request volume *and* failure-aware account l
|
|
|
1268
1305
|
- [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md) — gate a route behind a valid token (`authMiddleware`); stack the throttle in front of it on login routes.
|
|
1269
1306
|
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — the login flow whose failures this middleware counts.
|
|
1270
1307
|
- [`@warlock.js/core/use-middleware/SKILL.md`](@warlock.js/core/use-middleware/SKILL.md) — `middleware.rateLimit` and the built-in middleware suite.
|
|
1271
|
-
|
|
1272
|
-
|
|
1308
|
+
|
|
1309
|
+
|
package/llms.txt
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
# Warlock Auth
|
|
2
|
-
|
|
3
|
-
> Package: `@warlock.js/auth`
|
|
4
|
-
|
|
5
|
-
> Authentication system for Warlock.js applications
|
|
6
|
-
|
|
7
|
-
## Skills
|
|
8
|
-
|
|
9
|
-
- [auth-basics](@warlock.js/auth/auth-basics/SKILL.md):
|
|
10
|
-
- [customize-token-storage](@warlock.js/auth/customize-token-storage/SKILL.md):
|
|
11
|
-
- [customize-user-type](@warlock.js/auth/customize-user-type/SKILL.md):
|
|
12
|
-
- [handle-login-and-logout](@warlock.js/auth/handle-login-and-logout/SKILL.md):
|
|
13
|
-
- [manage-tokens](@warlock.js/auth/manage-tokens/SKILL.md):
|
|
14
|
-
- [overview](@warlock.js/auth/overview/SKILL.md):
|
|
15
|
-
- [protect-routes](@warlock.js/auth/protect-routes/SKILL.md):
|
|
16
|
-
- [register-user](@warlock.js/auth/register-user/SKILL.md): Sign up a new user and issue the initial token pair — User.create({...password: await hashPassword(plain)}) then authService.createTokenPair(user). Triggers: `User.create`, `hashPassword`, `verifyPassword`, `authService.createTokenPair`, `toJsonColumns`, `strongPassword`, `authEvents`; "build a register endpoint", "POST /register controller", "sign up a new user", "hash password on signup", "email verification flow"; typical import `import { authService } from "@warlock.js/auth"; import { hashPassword } from "@warlock.js/core"`. Skip: login — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; token internals — `@warlock.js/auth/manage-tokens/SKILL.md`; competing libs `bcrypt`, `bcryptjs`, `argon2`.
|
|
17
|
-
- [run-auth-commands](@warlock.js/auth/run-auth-commands/SKILL.md):
|
|
18
|
-
- [throttle-login-attempts](@warlock.js/auth/throttle-login-attempts/SKILL.md):
|
|
1
|
+
# Warlock Auth
|
|
2
|
+
|
|
3
|
+
> Package: `@warlock.js/auth`
|
|
4
|
+
|
|
5
|
+
> Authentication system for Warlock.js applications
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
- [auth-basics](@warlock.js/auth/auth-basics/SKILL.md): (no description)
|
|
10
|
+
- [customize-token-storage](@warlock.js/auth/customize-token-storage/SKILL.md): (no description)
|
|
11
|
+
- [customize-user-type](@warlock.js/auth/customize-user-type/SKILL.md): (no description)
|
|
12
|
+
- [handle-login-and-logout](@warlock.js/auth/handle-login-and-logout/SKILL.md): (no description)
|
|
13
|
+
- [manage-tokens](@warlock.js/auth/manage-tokens/SKILL.md): (no description)
|
|
14
|
+
- [overview](@warlock.js/auth/overview/SKILL.md): (no description)
|
|
15
|
+
- [protect-routes](@warlock.js/auth/protect-routes/SKILL.md): (no description)
|
|
16
|
+
- [register-user](@warlock.js/auth/register-user/SKILL.md): Sign up a new user and issue the initial token pair — User.create({...password: await hashPassword(plain)}) then authService.createTokenPair(user). Triggers: `User.create`, `hashPassword`, `verifyPassword`, `authService.createTokenPair`, `toJsonColumns`, `strongPassword`, `authEvents`; "build a register endpoint", "POST /register controller", "sign up a new user", "hash password on signup", "email verification flow"; typical import `import { authService } from "@warlock.js/auth"; import { hashPassword } from "@warlock.js/core"`. Skip: login — `@warlock.js/auth/handle-login-and-logout/SKILL.md`; token internals — `@warlock.js/auth/manage-tokens/SKILL.md`; competing libs `bcrypt`, `bcryptjs`, `argon2`.
|
|
17
|
+
- [run-auth-commands](@warlock.js/auth/run-auth-commands/SKILL.md): (no description)
|
|
18
|
+
- [throttle-login-attempts](@warlock.js/auth/throttle-login-attempts/SKILL.md): (no description)
|
package/package.json
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"ms": "^2.1.3"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@warlock.js/fs": "4.
|
|
27
|
-
"@warlock.js/cache": "4.
|
|
28
|
-
"@warlock.js/cascade": "4.
|
|
29
|
-
"@warlock.js/core": "4.
|
|
30
|
-
"@warlock.js/logger": "4.
|
|
31
|
-
"@warlock.js/seal": "4.
|
|
26
|
+
"@warlock.js/fs": "4.10.0",
|
|
27
|
+
"@warlock.js/cache": "4.10.0",
|
|
28
|
+
"@warlock.js/cascade": "4.10.0",
|
|
29
|
+
"@warlock.js/core": "4.10.0",
|
|
30
|
+
"@warlock.js/logger": "4.10.0",
|
|
31
|
+
"@warlock.js/seal": "4.10.0"
|
|
32
32
|
},
|
|
33
|
-
"version": "4.
|
|
33
|
+
"version": "4.10.0",
|
|
34
34
|
"type": "module",
|
|
35
35
|
"main": "./esm/index.mjs",
|
|
36
36
|
"module": "./esm/index.mjs",
|
|
@@ -92,6 +92,42 @@ async function feedController(request: Request, response: Response) {
|
|
|
92
92
|
|
|
93
93
|
The middleware uses the framework's `response.unauthorized({...})` shape. To override the response globally, hook the framework's error transformer to remap `AuthErrorCodes.*` codes.
|
|
94
94
|
|
|
95
|
+
## When flat user types aren't enough — reach for `@warlock.js/access`
|
|
96
|
+
|
|
97
|
+
`authMiddleware` gates on **user type** by flat string membership. That answers "is this an admin?" and nothing more. It deliberately cannot express:
|
|
98
|
+
|
|
99
|
+
- a permission matrix (`articles.publish` rather than `"editor"`)
|
|
100
|
+
- role hierarchy or inheritance
|
|
101
|
+
- **who may act on whom** — "an `admin` may create a `teacher`, but only a `superAdmin` may create or promote an `admin`"
|
|
102
|
+
|
|
103
|
+
That last one is the difference between a role check and an authorization model: without it, an account-creation endpoint gated on `authMiddleware("admin")` is a privilege-escalation path.
|
|
104
|
+
|
|
105
|
+
Install [`@warlock.js/access`](@warlock.js/access/overview/SKILL.md) for that. It layers RBAC plus per-permission ABAC policies over the same authenticated user:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { gate, can, definePolicy } from "@warlock.js/access";
|
|
109
|
+
|
|
110
|
+
// Permission-based route gate, in place of a user-type gate
|
|
111
|
+
router.post("/articles", articlesController, { middleware: [authMiddleware([]), gate("articles.create")] });
|
|
112
|
+
|
|
113
|
+
// Who-may-act-on-whom: an ABAC policy on top of the RBAC grant
|
|
114
|
+
definePolicy("users.create", (actor, target, ctx) =>
|
|
115
|
+
ctx.hasRole("superAdmin") || (target as User).userType === "teacher",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (await can(request.user, "users.create", { resource: payload })) { /* ... */ }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Use `authMiddleware` to establish *who the caller is*, and `access` to decide *what they may do*. They compose — `access` reads the user `authMiddleware` put on the request.
|
|
122
|
+
|
|
123
|
+
## Sessions are bearer-token, not cookie
|
|
124
|
+
|
|
125
|
+
The token is read from the `Authorization` header (`Bearer <token>` or `Key <token>`) and nowhere else. "Session" in this package means a **refresh-token row**, not an HTTP cookie session.
|
|
126
|
+
|
|
127
|
+
There is no cookie-backed guard, no session store, and no cookie strategy in `authMiddleware` — that is the intended design, not an omission. Warlock's auth optimises for API services.
|
|
128
|
+
|
|
129
|
+
If you want HttpOnly cookie sessions for a server-rendered admin, that is **app-level work**: set the cookie yourself with `response.cookie` (secure by default since 4.10.0) and read it in your own middleware, then hand the token to the same `authService` rotation logic. **You also own CSRF** — the framework ships no CSRF helper, and cookie auth needs one where bearer auth does not. A synchronizer token bound to the session plus `Origin`/`Referer` checks is the usual shape.
|
|
130
|
+
|
|
95
131
|
## Things NOT to do
|
|
96
132
|
|
|
97
133
|
- Don't call `authMiddleware` outside route definition. It returns a function — the function is what runs per-request. Calling it once per request creates a fresh middleware on every hit (wasteful) and a fresh allowed-types Set (correctness if the input changes per call).
|
|
@@ -104,3 +140,4 @@ The middleware uses the framework's `response.unauthorized({...})` shape. To ove
|
|
|
104
140
|
- [`@warlock.js/auth/customize-user-type/SKILL.md`](@warlock.js/auth/customize-user-type/SKILL.md) — config and multi-user-type semantics
|
|
105
141
|
- [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — where the access token gets issued in the first place
|
|
106
142
|
- [`@warlock.js/auth/throttle-login-attempts/SKILL.md`](@warlock.js/auth/throttle-login-attempts/SKILL.md) — brute-force throttle to stack in front of the login route
|
|
143
|
+
- [`@warlock.js/access/overview/SKILL.md`](@warlock.js/access/overview/SKILL.md) — permissions, roles, and who-may-act-on-whom policies, when user-type gating isn't enough
|