@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.
- package/CHANGELOG.md +56 -56
- package/LICENSE +21 -21
- package/README.md +78 -78
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
- package/esm/models/access-token/access-token.model.mjs.map +1 -1
- package/esm/models/access-token/migration.mjs.map +1 -1
- package/esm/models/refresh-token/migration.mjs.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
- package/esm/services/auth-config.mjs.map +1 -1
- package/esm/services/auth-events.mjs.map +1 -1
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/utils/auth-error-codes.mjs.map +1 -1
- package/llms-full.txt +1272 -1272
- package/llms.txt +18 -18
- package/package.json +7 -7
- package/skills/auth-basics/SKILL.md +89 -89
- package/skills/customize-token-storage/SKILL.md +112 -112
- package/skills/customize-user-type/SKILL.md +142 -142
- package/skills/handle-login-and-logout/SKILL.md +160 -160
- package/skills/manage-tokens/SKILL.md +169 -169
- package/skills/overview/SKILL.md +72 -72
- package/skills/protect-routes/SKILL.md +106 -106
- package/skills/run-auth-commands/SKILL.md +125 -125
- package/skills/throttle-login-attempts/SKILL.md +116 -116
|
@@ -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
|
package/skills/overview/SKILL.md
CHANGED
|
@@ -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/`.
|