@warlock.js/auth 4.2.11 → 4.3.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.
@@ -1,169 +1,169 @@
1
- ---
2
- name: manage-tokens
3
- 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`.'
4
- ---
5
-
6
- # Manage tokens
7
-
8
- Tokens are persisted Cascade models. Issuing a token writes a row. Verification checks the row exists. Revocation deletes / marks-revoked. This gives you JWT's stateless verification + statelful revocation.
9
-
10
- ## Token shapes
11
-
12
- ```ts
13
- type AccessTokenOutput = { token: string; expiresAt: string };
14
- type RefreshTokenOutput = { token: string; expiresAt: string };
15
-
16
- type TokenPair = {
17
- accessToken: AccessTokenOutput;
18
- refreshToken?: RefreshTokenOutput; // omitted if config.auth.refreshToken.enabled = false
19
- };
20
- ```
21
-
22
- ## Issuing tokens
23
-
24
- ```ts
25
- import { authService } from "@warlock.js/auth";
26
-
27
- // Just an access token (rare — usually use createTokenPair)
28
- const access = await authService.generateAccessToken(user);
29
-
30
- // Just a refresh token
31
- const refresh = await authService.createRefreshToken(user, deviceInfo);
32
-
33
- // Both — the everyday case
34
- const pair = await authService.createTokenPair(user, deviceInfo);
35
- ```
36
-
37
- `createTokenPair` is the typical issuance path. It respects `config.auth.refreshToken.enabled` — if disabled, returns only `accessToken`.
38
-
39
- ## Refresh with rotation — `refreshTokens`
40
-
41
- ```ts
42
- const next = await authService.refreshTokens(oldRefreshToken, deviceInfo);
43
- // next: TokenPair | null
44
- ```
45
-
46
- What happens internally:
47
-
48
- 1. Verify the JWT signature on the old refresh token.
49
- 2. Find the row in `RefreshToken` — must exist + not be revoked.
50
- 3. Look up the user via `config.auth.userType[token.userType]`.
51
- 4. **Rotation** (default — `config.auth.refreshToken.rotation = true`): revoke the old refresh token, create a new pair from the same `family_id`.
52
- 5. **No rotation**: mark the old as "used" but keep it valid.
53
-
54
- **Replay detection.** If the old refresh token is presented again after rotation (already revoked but still in the DB):
55
-
56
- ```ts
57
- // Inside refreshTokens, on a revoked-token presentation:
58
- await authService.revokeTokenFamily(refreshToken.get("family_id"));
59
- ```
60
-
61
- Every refresh token in the same family is revoked. Pattern: a leaked refresh token is used by both legitimate user and attacker — the second use triggers the revoke, both sides get kicked.
62
-
63
- ## Family — the rotation chain
64
-
65
- ```
66
- login → creates family X — refresh token A in family X
67
- refresh (A) → revokes A; creates B in family X
68
- refresh (B) → revokes B; creates C in family X
69
- refresh (A again)→ A is revoked → revoke family X entirely
70
- ```
71
-
72
- The family ties together "successive rotations of one session." Logout of one device kills only that device's family — other devices keep their own families.
73
-
74
- ## Listing active sessions
75
-
76
- ```ts
77
- const sessions = await authService.getActiveSessions(user);
78
-
79
- for (const session of sessions) {
80
- session.get("device_info"); // { userAgent, ip, deviceId? } if provided at login
81
- session.get("created_at");
82
- session.get("expires_at");
83
- }
84
- ```
85
-
86
- Use this for "active sessions" UIs. Revoke a specific session by calling `.revoke()` on the `RefreshToken` instance.
87
-
88
- ## Removing tokens
89
-
90
- ```ts
91
- // Specific access token
92
- await authService.removeAccessToken(user, accessTokenString);
93
-
94
- // Specific refresh token (via the RefreshToken instance)
95
- const rt = await RefreshToken.findByToken(refreshString);
96
- await rt?.revoke();
97
-
98
- // All access tokens for a user
99
- await authService.removeAllAccessTokens(user);
100
-
101
- // Everything — access + refresh + family
102
- await authService.revokeAllTokens(user);
103
-
104
- // A specific family
105
- await authService.revokeTokenFamily(familyId);
106
- ```
107
-
108
- ## Max refresh tokens per user
109
-
110
- ```ts
111
- // In config.auth.refreshToken:
112
- {
113
- maxPerUser: 5, // default
114
- }
115
- ```
116
-
117
- When issuing a new refresh token, the service counts active tokens for the user and revokes the oldest until count < `maxPerUser`. Pattern: limits how many simultaneous sessions a user can hold; prevents an attacker who got a token from gradually accumulating many.
118
-
119
- ## Expired-token cleanup
120
-
121
- ```ts
122
- const cleaned = await authService.cleanupExpiredTokens();
123
- // Returns: number of expired refresh tokens removed (also purges expired access-token rows).
124
- // Fires "token.expired" event per token + "cleanup.completed" with the count.
125
- ```
126
-
127
- Run this periodically via the scheduler:
128
-
129
- ```ts
130
- import { scheduler, job } from "@warlock.js/scheduler";
131
- import { authService } from "@warlock.js/auth";
132
-
133
- scheduler.addJob(
134
- job("auth-cleanup", () => authService.cleanupExpiredTokens())
135
- .daily()
136
- .at("03:00"),
137
- );
138
- ```
139
-
140
- Or use the bundled CLI command — see [`@warlock.js/auth/run-auth-commands/SKILL.md`](@warlock.js/auth/run-auth-commands/SKILL.md).
141
-
142
- ## JWT helpers
143
-
144
- For low-level JWT signing/verification (outside the authService flow):
145
-
146
- ```ts
147
- import { jwt } from "@warlock.js/auth";
148
-
149
- const token = await jwt.generate(payload, { expiresIn: "1h" }); // string or ms; a bare number is milliseconds
150
- const decoded = await jwt.verify(token);
151
-
152
- const refreshToken = await jwt.generateRefreshToken(payload, { expiresIn });
153
- const decodedRefresh = await jwt.verifyRefreshToken(refreshToken);
154
- ```
155
-
156
- The package signs access and refresh tokens with independent secrets — `config.auth.accessToken.secret` and `config.auth.refreshToken.secret`. Setting a distinct `refresh.secret` is recommended: it prevents an access-token compromise from forging refresh tokens (and vice versa). The refresh secret is **optional** — when `config.auth.refreshToken.secret` is unset, refresh tokens fall back to the main `config.auth.accessToken.secret`, so refresh works out of the box without a second secret.
157
-
158
- ## Things NOT to do
159
-
160
- - Don't use raw JWT libraries directly. The package handles signing, verification, secret loading, and the access/refresh split.
161
- - Don't disable rotation (`config.auth.refreshToken.rotation = false`) unless you genuinely understand the tradeoff — you lose replay detection.
162
- - Don't increase `maxPerUser` to a huge number "to be safe." Each active refresh token is a revocation surface; fewer simultaneous tokens means less attack surface.
163
- - Don't manually delete `AccessToken` rows in a service. The user might be hitting a request mid-revoke and get an inconsistent state. Use the `authService` helpers.
164
-
165
- ## See also
166
-
167
- - [`@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
168
- - [`@warlock.js/auth/run-auth-commands/SKILL.md`](@warlock.js/auth/run-auth-commands/SKILL.md) — the bundled cleanup command
169
- - [`@warlock.js/scheduler/scheduler-basics/SKILL.md`](@warlock.js/scheduler/scheduler-basics/SKILL.md) — scheduling cleanup
1
+ ---
2
+ name: manage-tokens
3
+ 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`.'
4
+ ---
5
+
6
+ # Manage tokens
7
+
8
+ Tokens are persisted Cascade models. Issuing a token writes a row. Verification checks the row exists. Revocation deletes / marks-revoked. This gives you JWT's stateless verification + statelful revocation.
9
+
10
+ ## Token shapes
11
+
12
+ ```ts
13
+ type AccessTokenOutput = { token: string; expiresAt: string };
14
+ type RefreshTokenOutput = { token: string; expiresAt: string };
15
+
16
+ type TokenPair = {
17
+ accessToken: AccessTokenOutput;
18
+ refreshToken?: RefreshTokenOutput; // omitted if config.auth.refreshToken.enabled = false
19
+ };
20
+ ```
21
+
22
+ ## Issuing tokens
23
+
24
+ ```ts
25
+ import { authService } from "@warlock.js/auth";
26
+
27
+ // Just an access token (rare — usually use createTokenPair)
28
+ const access = await authService.generateAccessToken(user);
29
+
30
+ // Just a refresh token
31
+ const refresh = await authService.createRefreshToken(user, deviceInfo);
32
+
33
+ // Both — the everyday case
34
+ const pair = await authService.createTokenPair(user, deviceInfo);
35
+ ```
36
+
37
+ `createTokenPair` is the typical issuance path. It respects `config.auth.refreshToken.enabled` — if disabled, returns only `accessToken`.
38
+
39
+ ## Refresh with rotation — `refreshTokens`
40
+
41
+ ```ts
42
+ const next = await authService.refreshTokens(oldRefreshToken, deviceInfo);
43
+ // next: TokenPair | null
44
+ ```
45
+
46
+ What happens internally:
47
+
48
+ 1. Verify the JWT signature on the old refresh token.
49
+ 2. Find the row in `RefreshToken` — must exist + not be revoked.
50
+ 3. Look up the user via `config.auth.userType[token.userType]`.
51
+ 4. **Rotation** (default — `config.auth.refreshToken.rotation = true`): revoke the old refresh token, create a new pair from the same `family_id`.
52
+ 5. **No rotation**: mark the old as "used" but keep it valid.
53
+
54
+ **Replay detection.** If the old refresh token is presented again after rotation (already revoked but still in the DB):
55
+
56
+ ```ts
57
+ // Inside refreshTokens, on a revoked-token presentation:
58
+ await authService.revokeTokenFamily(refreshToken.get("family_id"));
59
+ ```
60
+
61
+ Every refresh token in the same family is revoked. Pattern: a leaked refresh token is used by both legitimate user and attacker — the second use triggers the revoke, both sides get kicked.
62
+
63
+ ## Family — the rotation chain
64
+
65
+ ```
66
+ login → creates family X — refresh token A in family X
67
+ refresh (A) → revokes A; creates B in family X
68
+ refresh (B) → revokes B; creates C in family X
69
+ refresh (A again)→ A is revoked → revoke family X entirely
70
+ ```
71
+
72
+ The family ties together "successive rotations of one session." Logout of one device kills only that device's family — other devices keep their own families.
73
+
74
+ ## Listing active sessions
75
+
76
+ ```ts
77
+ const sessions = await authService.getActiveSessions(user);
78
+
79
+ for (const session of sessions) {
80
+ session.get("device_info"); // { userAgent, ip, deviceId? } if provided at login
81
+ session.get("created_at");
82
+ session.get("expires_at");
83
+ }
84
+ ```
85
+
86
+ Use this for "active sessions" UIs. Revoke a specific session by calling `.revoke()` on the `RefreshToken` instance.
87
+
88
+ ## Removing tokens
89
+
90
+ ```ts
91
+ // Specific access token
92
+ await authService.removeAccessToken(user, accessTokenString);
93
+
94
+ // Specific refresh token (via the RefreshToken instance)
95
+ const rt = await RefreshToken.findByToken(refreshString);
96
+ await rt?.revoke();
97
+
98
+ // All access tokens for a user
99
+ await authService.removeAllAccessTokens(user);
100
+
101
+ // Everything — access + refresh + family
102
+ await authService.revokeAllTokens(user);
103
+
104
+ // A specific family
105
+ await authService.revokeTokenFamily(familyId);
106
+ ```
107
+
108
+ ## Max refresh tokens per user
109
+
110
+ ```ts
111
+ // In config.auth.refreshToken:
112
+ {
113
+ maxPerUser: 5, // default
114
+ }
115
+ ```
116
+
117
+ When issuing a new refresh token, the service counts active tokens for the user and revokes the oldest until count < `maxPerUser`. Pattern: limits how many simultaneous sessions a user can hold; prevents an attacker who got a token from gradually accumulating many.
118
+
119
+ ## Expired-token cleanup
120
+
121
+ ```ts
122
+ const cleaned = await authService.cleanupExpiredTokens();
123
+ // Returns: number of expired refresh tokens removed (also purges expired access-token rows).
124
+ // Fires "token.expired" event per token + "cleanup.completed" with the count.
125
+ ```
126
+
127
+ Run this periodically via the scheduler:
128
+
129
+ ```ts
130
+ import { scheduler, job } from "@warlock.js/scheduler";
131
+ import { authService } from "@warlock.js/auth";
132
+
133
+ scheduler.addJob(
134
+ job("auth-cleanup", () => authService.cleanupExpiredTokens())
135
+ .daily()
136
+ .at("03:00"),
137
+ );
138
+ ```
139
+
140
+ Or use the bundled CLI command — see [`@warlock.js/auth/run-auth-commands/SKILL.md`](@warlock.js/auth/run-auth-commands/SKILL.md).
141
+
142
+ ## JWT helpers
143
+
144
+ For low-level JWT signing/verification (outside the authService flow):
145
+
146
+ ```ts
147
+ import { jwt } from "@warlock.js/auth";
148
+
149
+ const token = await jwt.generate(payload, { expiresIn: "1h" }); // string or ms; a bare number is milliseconds
150
+ const decoded = await jwt.verify(token);
151
+
152
+ const refreshToken = await jwt.generateRefreshToken(payload, { expiresIn });
153
+ const decodedRefresh = await jwt.verifyRefreshToken(refreshToken);
154
+ ```
155
+
156
+ The package signs access and refresh tokens with independent secrets — `config.auth.accessToken.secret` and `config.auth.refreshToken.secret`. Setting a distinct `refresh.secret` is recommended: it prevents an access-token compromise from forging refresh tokens (and vice versa). The refresh secret is **optional** — when `config.auth.refreshToken.secret` is unset, refresh tokens fall back to the main `config.auth.accessToken.secret`, so refresh works out of the box without a second secret.
157
+
158
+ ## Things NOT to do
159
+
160
+ - Don't use raw JWT libraries directly. The package handles signing, verification, secret loading, and the access/refresh split.
161
+ - Don't disable rotation (`config.auth.refreshToken.rotation = false`) unless you genuinely understand the tradeoff — you lose replay detection.
162
+ - Don't increase `maxPerUser` to a huge number "to be safe." Each active refresh token is a revocation surface; fewer simultaneous tokens means less attack surface.
163
+ - Don't manually delete `AccessToken` rows in a service. The user might be hitting a request mid-revoke and get an inconsistent state. Use the `authService` helpers.
164
+
165
+ ## See also
166
+
167
+ - [`@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
168
+ - [`@warlock.js/auth/run-auth-commands/SKILL.md`](@warlock.js/auth/run-auth-commands/SKILL.md) — the bundled cleanup command
169
+ - [`@warlock.js/scheduler/scheduler-basics/SKILL.md`](@warlock.js/scheduler/scheduler-basics/SKILL.md) — scheduling cleanup
@@ -1,72 +1,72 @@
1
- ---
2
- name: overview
3
- 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).'
4
- ---
5
-
6
- # `@warlock.js/auth` — overview
7
-
8
- JWT authentication for Warlock apps. You get a base `Auth` model your user types extend, an `authMiddleware` route gate, an `authService` that runs login/logout/refresh (with refresh-token rotation and replay detection), persisted access + refresh tokens, multi-user-type support, lifecycle events, and two bundled CLI commands.
9
-
10
- Coupled to `@warlock.js/core` — you're inside a Warlock project before this package makes sense.
11
-
12
- ## When to reach for it
13
-
14
- - You're building a Warlock app that needs login, protected routes, and token-based sessions.
15
- - You need **multiple user types** (admins + regular users, or client/vendor/staff personas) gated separately on the same auth system.
16
- - You want refresh-token **rotation + replay detection** out of the box rather than hand-rolling token security.
17
-
18
- Skip if you're not on `@warlock.js/core` (the package depends on it), or if you need session-cookie auth rather than JWTs.
19
-
20
- ## The mental model in one paragraph
21
-
22
- Your user model extends the `Auth` base model and declares its `userType`. A login flows through `authService.login(Model, credentials, deviceInfo?)`: it verifies the password, issues an access + refresh token pair (persisted as `AccessToken` / `RefreshToken` records), and fires events. `authMiddleware(allowedUserType)` gates routes — the argument is required and always requires a valid token: `[]` allows any authenticated user, a user-type argument restricts to those types (401 otherwise). There is no anonymous mode; public routes simply omit the middleware. Refresh rotates the refresh token and detects replay by revoking the whole token family. CLI commands generate the JWT secret and clean up expired tokens.
23
-
24
- ## Skills index
25
-
26
- Nine task skills. Most apps need `auth-basics` + `protect-routes` + `handle-login-and-logout`.
27
-
28
- ### Foundations
29
-
30
- #### [`auth-basics`](@warlock.js/auth/auth-basics/SKILL.md)
31
- Start here. The `Auth` base model, `authMiddleware` gate, `authService` (login/logout/refresh), AccessToken + RefreshToken persistence, multi-user-type support.
32
-
33
- ### The flows
34
-
35
- #### [`handle-login-and-logout`](@warlock.js/auth/handle-login-and-logout/SKILL.md)
36
- `authService.login(Model, credentials, deviceInfo?)` — verify password, issue the token pair, fire events. `authService.logout(user, accessToken?, refreshToken?)` — revoke tokens. For your `POST /login` and `POST /logout` controllers.
37
-
38
- #### [`register-user`](@warlock.js/auth/register-user/SKILL.md)
39
- Sign up a new user and issue the first token pair — `User.create({ ...password: await hashPassword(plain) })` then `authService.createTokenPair(user)`. For `POST /register`.
40
-
41
- #### [`protect-routes`](@warlock.js/auth/protect-routes/SKILL.md)
42
- `authMiddleware(allowedUserType)` — the argument is required and always requires a valid token: `[]` allows any authenticated user, a user-type argument restricts to those types. Sets `request.user` + `request.decodedAccessToken`, responds 401 on failure.
43
-
44
- ### Going deeper
45
-
46
- #### [`manage-tokens`](@warlock.js/auth/manage-tokens/SKILL.md)
47
- The token lifecycle — `generateAccessToken`, `createRefreshToken`, `createTokenPair`, `refreshTokens` (rotation + replay detection), `revokeAllTokens`, `revokeTokenFamily`, `cleanupExpiredTokens`, `getActiveSessions`. For custom login/registration, token revocation, "logout everywhere", and scheduled cleanup.
48
-
49
- #### [`customize-user-type`](@warlock.js/auth/customize-user-type/SKILL.md)
50
- Support multiple user types in one system — each `Auth` subclass overrides `userType`, `config.auth.userType.<slug>` maps the slug to a model class, `authMiddleware("admin")` / `authMiddleware(["admin", "staff"])` gates per type.
51
-
52
- #### [`customize-token-storage`](@warlock.js/auth/customize-token-storage/SKILL.md)
53
- Override the persisted `AccessToken` / `RefreshToken` models to add columns (multi-tenant `organization_id`), rename, or change storage — extend the model + schema and register it under `config.auth.accessToken.model` / `config.auth.refreshToken.model`.
54
-
55
- #### [`throttle-login-attempts`](@warlock.js/auth/throttle-login-attempts/SKILL.md)
56
- Brute-force / credential-stuffing protection — `loginThrottleMiddleware()` counts only failed logins, resets on success, locks per-account + per-IP after a threshold, and rejects pre-controller with 429. Cache-backed, fails open on a cache outage.
57
-
58
- #### [`run-auth-commands`](@warlock.js/auth/run-auth-commands/SKILL.md)
59
- Two CLI commands — `warlock jwt.generate` (strong JWT secret → `.env`) and `warlock auth.cleanup` (remove expired refresh tokens). Register via `registerJWTSecretGeneratorCommand()` and `registerAuthCleanupCommand()`.
60
-
61
- ## What this package deliberately doesn't do
62
-
63
- - **Session-cookie auth.** It's JWT/token-based. If you need server-side sessions, this isn't it.
64
- - **OAuth / social login / SSO.** No provider adapters here — wire those at the controller layer and create the user through this package's models.
65
- - **Authorization / roles / permissions (RBAC).** It authenticates (who you are) and gates by user *type*, not fine-grained permissions. Build RBAC on top.
66
- - **Standalone use.** It depends on `@warlock.js/core` for routing, models (Cascade), and config.
67
-
68
- ## See also
69
-
70
- - [`@warlock.js/core/warlock-conventions/SKILL.md`](@warlock.js/core/warlock-conventions/SKILL.md) — the framework auth runs inside (routing, middleware, config).
71
- - [`@warlock.js/cascade/cascade-basics/SKILL.md`](@warlock.js/cascade/cascade-basics/SKILL.md) — the ORM behind the `Auth`, `AccessToken`, and `RefreshToken` models.
72
- - `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this `overview/SKILL.md` becomes `.claude/skills/warlock-js-auth-overview/`.
1
+ ---
2
+ name: overview
3
+ 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).'
4
+ ---
5
+
6
+ # `@warlock.js/auth` — overview
7
+
8
+ JWT authentication for Warlock apps. You get a base `Auth` model your user types extend, an `authMiddleware` route gate, an `authService` that runs login/logout/refresh (with refresh-token rotation and replay detection), persisted access + refresh tokens, multi-user-type support, lifecycle events, and two bundled CLI commands.
9
+
10
+ Coupled to `@warlock.js/core` — you're inside a Warlock project before this package makes sense.
11
+
12
+ ## When to reach for it
13
+
14
+ - You're building a Warlock app that needs login, protected routes, and token-based sessions.
15
+ - You need **multiple user types** (admins + regular users, or client/vendor/staff personas) gated separately on the same auth system.
16
+ - You want refresh-token **rotation + replay detection** out of the box rather than hand-rolling token security.
17
+
18
+ Skip if you're not on `@warlock.js/core` (the package depends on it), or if you need session-cookie auth rather than JWTs.
19
+
20
+ ## The mental model in one paragraph
21
+
22
+ Your user model extends the `Auth` base model and declares its `userType`. A login flows through `authService.login(Model, credentials, deviceInfo?)`: it verifies the password, issues an access + refresh token pair (persisted as `AccessToken` / `RefreshToken` records), and fires events. `authMiddleware(allowedUserType)` gates routes — the argument is required and always requires a valid token: `[]` allows any authenticated user, a user-type argument restricts to those types (401 otherwise). There is no anonymous mode; public routes simply omit the middleware. Refresh rotates the refresh token and detects replay by revoking the whole token family. CLI commands generate the JWT secret and clean up expired tokens.
23
+
24
+ ## Skills index
25
+
26
+ Nine task skills. Most apps need `auth-basics` + `protect-routes` + `handle-login-and-logout`.
27
+
28
+ ### Foundations
29
+
30
+ #### [`auth-basics`](@warlock.js/auth/auth-basics/SKILL.md)
31
+ Start here. The `Auth` base model, `authMiddleware` gate, `authService` (login/logout/refresh), AccessToken + RefreshToken persistence, multi-user-type support.
32
+
33
+ ### The flows
34
+
35
+ #### [`handle-login-and-logout`](@warlock.js/auth/handle-login-and-logout/SKILL.md)
36
+ `authService.login(Model, credentials, deviceInfo?)` — verify password, issue the token pair, fire events. `authService.logout(user, accessToken?, refreshToken?)` — revoke tokens. For your `POST /login` and `POST /logout` controllers.
37
+
38
+ #### [`register-user`](@warlock.js/auth/register-user/SKILL.md)
39
+ Sign up a new user and issue the first token pair — `User.create({ ...password: await hashPassword(plain) })` then `authService.createTokenPair(user)`. For `POST /register`.
40
+
41
+ #### [`protect-routes`](@warlock.js/auth/protect-routes/SKILL.md)
42
+ `authMiddleware(allowedUserType)` — the argument is required and always requires a valid token: `[]` allows any authenticated user, a user-type argument restricts to those types. Sets `request.user` + `request.decodedAccessToken`, responds 401 on failure.
43
+
44
+ ### Going deeper
45
+
46
+ #### [`manage-tokens`](@warlock.js/auth/manage-tokens/SKILL.md)
47
+ The token lifecycle — `generateAccessToken`, `createRefreshToken`, `createTokenPair`, `refreshTokens` (rotation + replay detection), `revokeAllTokens`, `revokeTokenFamily`, `cleanupExpiredTokens`, `getActiveSessions`. For custom login/registration, token revocation, "logout everywhere", and scheduled cleanup.
48
+
49
+ #### [`customize-user-type`](@warlock.js/auth/customize-user-type/SKILL.md)
50
+ Support multiple user types in one system — each `Auth` subclass overrides `userType`, `config.auth.userType.<slug>` maps the slug to a model class, `authMiddleware("admin")` / `authMiddleware(["admin", "staff"])` gates per type.
51
+
52
+ #### [`customize-token-storage`](@warlock.js/auth/customize-token-storage/SKILL.md)
53
+ Override the persisted `AccessToken` / `RefreshToken` models to add columns (multi-tenant `organization_id`), rename, or change storage — extend the model + schema and register it under `config.auth.accessToken.model` / `config.auth.refreshToken.model`.
54
+
55
+ #### [`throttle-login-attempts`](@warlock.js/auth/throttle-login-attempts/SKILL.md)
56
+ Brute-force / credential-stuffing protection — `loginThrottleMiddleware()` counts only failed logins, resets on success, locks per-account + per-IP after a threshold, and rejects pre-controller with 429. Cache-backed, fails open on a cache outage.
57
+
58
+ #### [`run-auth-commands`](@warlock.js/auth/run-auth-commands/SKILL.md)
59
+ Two CLI commands — `warlock jwt.generate` (strong JWT secret → `.env`) and `warlock auth.cleanup` (remove expired refresh tokens). Register via `registerJWTSecretGeneratorCommand()` and `registerAuthCleanupCommand()`.
60
+
61
+ ## What this package deliberately doesn't do
62
+
63
+ - **Session-cookie auth.** It's JWT/token-based. If you need server-side sessions, this isn't it.
64
+ - **OAuth / social login / SSO.** No provider adapters here — wire those at the controller layer and create the user through this package's models.
65
+ - **Authorization / roles / permissions (RBAC).** It authenticates (who you are) and gates by user *type*, not fine-grained permissions. Build RBAC on top.
66
+ - **Standalone use.** It depends on `@warlock.js/core` for routing, models (Cascade), and config.
67
+
68
+ ## See also
69
+
70
+ - [`@warlock.js/core/warlock-conventions/SKILL.md`](@warlock.js/core/warlock-conventions/SKILL.md) — the framework auth runs inside (routing, middleware, config).
71
+ - [`@warlock.js/cascade/cascade-basics/SKILL.md`](@warlock.js/cascade/cascade-basics/SKILL.md) — the ORM behind the `Auth`, `AccessToken`, and `RefreshToken` models.
72
+ - `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this `overview/SKILL.md` becomes `.claude/skills/warlock-js-auth-overview/`.