@warlock.js/auth 5.2.4 → 5.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/llms-full.txt
CHANGED
|
@@ -20,7 +20,7 @@ JWT-based authentication for Warlock. `Auth` base model + `authMiddleware` gate
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
pnpm add @warlock.js/auth
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Foundations
|
|
@@ -399,9 +399,10 @@ description: 'Run the full login flow via authService.login(Model, credentials,
|
|
|
399
399
|
|
|
400
400
|
```ts
|
|
401
401
|
import { authService } from "@warlock.js/auth";
|
|
402
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
402
403
|
import { User } from "@/app/users/models/user.model";
|
|
403
404
|
|
|
404
|
-
|
|
405
|
+
export const loginController: RequestHandler = async ({ request, response }) => {
|
|
405
406
|
const result = await authService.login(User, {
|
|
406
407
|
email: request.input("email"),
|
|
407
408
|
password: request.input("password"),
|
|
@@ -418,7 +419,7 @@ async function loginController(request: Request, response: Response) {
|
|
|
418
419
|
user: result.user,
|
|
419
420
|
tokens: result.tokens,
|
|
420
421
|
});
|
|
421
|
-
}
|
|
422
|
+
};
|
|
422
423
|
```
|
|
423
424
|
|
|
424
425
|
The returned shape:
|
|
@@ -470,7 +471,9 @@ Useful for "show active sessions" UIs — see `authService.getActiveSessions(use
|
|
|
470
471
|
## Logout — `authService.logout(user, accessToken?, refreshToken?)`
|
|
471
472
|
|
|
472
473
|
```ts
|
|
473
|
-
|
|
474
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
475
|
+
|
|
476
|
+
export const logoutController: RequestHandler = async ({ request, response }) => {
|
|
474
477
|
await authService.logout(
|
|
475
478
|
request.user!,
|
|
476
479
|
request.authorizationValue, // access token from the Authorization header
|
|
@@ -478,7 +481,7 @@ async function logoutController(request: Request, response: Response) {
|
|
|
478
481
|
);
|
|
479
482
|
|
|
480
483
|
return response.success({ message: "Logged out" });
|
|
481
|
-
}
|
|
484
|
+
};
|
|
482
485
|
```
|
|
483
486
|
|
|
484
487
|
The contract:
|
|
@@ -502,7 +505,9 @@ Useful for "logout from all devices" buttons. Fires `token.revoked` per token +
|
|
|
502
505
|
## Refresh tokens — `authService.refreshTokens(refreshTokenString, deviceInfo?)`
|
|
503
506
|
|
|
504
507
|
```ts
|
|
505
|
-
|
|
508
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
509
|
+
|
|
510
|
+
export const refreshController: RequestHandler = async ({ request, response }) => {
|
|
506
511
|
const tokens = await authService.refreshTokens(
|
|
507
512
|
request.input("refreshToken"),
|
|
508
513
|
{ userAgent: request.header("user-agent"), ip: request.ip },
|
|
@@ -513,7 +518,7 @@ async function refreshController(request: Request, response: Response) {
|
|
|
513
518
|
}
|
|
514
519
|
|
|
515
520
|
return response.success({ tokens });
|
|
516
|
-
}
|
|
521
|
+
};
|
|
517
522
|
```
|
|
518
523
|
|
|
519
524
|
Returns a new token pair or `null` (token expired, revoked, or replay-detected). With rotation enabled (default), the old refresh token is consumed; the new pair stays in the same "family." Replay → revoke the whole family. See [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md).
|
|
@@ -872,13 +877,15 @@ On failure, the middleware returns one of these 401 responses:
|
|
|
872
877
|
## Reading the user in a controller
|
|
873
878
|
|
|
874
879
|
```ts
|
|
875
|
-
|
|
880
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
881
|
+
|
|
882
|
+
export const accountController: RequestHandler = async ({ request, response }) => {
|
|
876
883
|
const user = request.user!; // typed via your Auth subclass
|
|
877
884
|
return response.success({
|
|
878
885
|
id: user.id,
|
|
879
886
|
email: user.get("email"),
|
|
880
887
|
});
|
|
881
|
-
}
|
|
888
|
+
};
|
|
882
889
|
```
|
|
883
890
|
|
|
884
891
|
Because the middleware always requires a valid token, `request.user` is guaranteed present inside any gated controller (the middleware would have responded 401 otherwise). The `!` is safe here.
|
|
@@ -899,10 +906,12 @@ Every route inside the group is gated — the group's `middleware` array applies
|
|
|
899
906
|
There is no "hydrate `request.user` if a token is present, otherwise continue" mode. `authMiddleware` always requires a valid token. If a route should be reachable anonymously, leave the middleware off — and read the token yourself in the controller if you want soft personalization:
|
|
900
907
|
|
|
901
908
|
```ts
|
|
902
|
-
|
|
909
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
910
|
+
|
|
911
|
+
export const feedController: RequestHandler = async ({ request, response }) => {
|
|
903
912
|
const token = request.authorizationValue;
|
|
904
913
|
// optionally decode/hydrate manually when a token is present
|
|
905
|
-
}
|
|
914
|
+
};
|
|
906
915
|
```
|
|
907
916
|
|
|
908
917
|
## Custom error responses
|
|
@@ -975,10 +984,10 @@ Two-step on the server: create the user (with hashed password), then issue token
|
|
|
975
984
|
|
|
976
985
|
```ts
|
|
977
986
|
import { authService } from "@warlock.js/auth";
|
|
978
|
-
import { hashPassword } from "@warlock.js/core";
|
|
987
|
+
import { hashPassword, type RequestHandler } from "@warlock.js/core";
|
|
979
988
|
import { User } from "@/app/users/models/user.model";
|
|
980
989
|
|
|
981
|
-
|
|
990
|
+
export const registerController: RequestHandler = async ({ request, response }) => {
|
|
982
991
|
const { email, password, name } = request.all();
|
|
983
992
|
|
|
984
993
|
// 1. Check duplicates
|
|
@@ -1005,7 +1014,7 @@ async function registerController(request: Request, response: Response) {
|
|
|
1005
1014
|
user, // shape via static toJsonColumns / static resource
|
|
1006
1015
|
tokens,
|
|
1007
1016
|
});
|
|
1008
|
-
}
|
|
1017
|
+
};
|
|
1009
1018
|
```
|
|
1010
1019
|
|
|
1011
1020
|
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.
|
|
@@ -1134,7 +1143,7 @@ export default defineConfig({
|
|
|
1134
1143
|
## `warlock jwt.generate` — JWT secret bootstrap
|
|
1135
1144
|
|
|
1136
1145
|
```bash
|
|
1137
|
-
|
|
1146
|
+
pnpm warlock jwt.generate
|
|
1138
1147
|
```
|
|
1139
1148
|
|
|
1140
1149
|
Generates a cryptographically strong secret string and writes it to your `.env` as `JWT_SECRET=...` (and `JWT_REFRESH_SECRET=...` if refresh tokens are enabled).
|
|
@@ -1146,7 +1155,7 @@ Run it once when setting up a new project. Each developer typically runs it loca
|
|
|
1146
1155
|
## `warlock auth.cleanup` — expired token sweep
|
|
1147
1156
|
|
|
1148
1157
|
```bash
|
|
1149
|
-
|
|
1158
|
+
pnpm warlock auth.cleanup
|
|
1150
1159
|
```
|
|
1151
1160
|
|
|
1152
1161
|
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.
|
|
@@ -1174,7 +1183,7 @@ In-process — no shell call. See [`@warlock.js/scheduler/scheduler-basics/SKILL
|
|
|
1174
1183
|
### Via system cron
|
|
1175
1184
|
|
|
1176
1185
|
```cron
|
|
1177
|
-
0 3 * * * cd /path/to/app && /usr/local/bin/
|
|
1186
|
+
0 3 * * * cd /path/to/app && /usr/local/bin/pnpm warlock auth.cleanup
|
|
1178
1187
|
```
|
|
1179
1188
|
|
|
1180
1189
|
Out-of-process — works when you don't want the scheduler subsystem running in this service.
|
|
@@ -1188,8 +1197,8 @@ If you have very-short-lived refresh tokens (1h expiry) and a million-user scale
|
|
|
1188
1197
|
## `warlock auth.purge-never-expiring` — one-off remediation
|
|
1189
1198
|
|
|
1190
1199
|
```bash
|
|
1191
|
-
|
|
1192
|
-
|
|
1200
|
+
pnpm warlock auth.purge-never-expiring --dry-run # report only
|
|
1201
|
+
pnpm warlock auth.purge-never-expiring # report, then revoke
|
|
1193
1202
|
```
|
|
1194
1203
|
|
|
1195
1204
|
Register with `registerAuthPurgeNeverExpiringCommand()`.
|
package/package.json
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"ms": "^2.1.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@warlock.js/fs": "5.
|
|
16
|
-
"@warlock.js/cache": "5.
|
|
17
|
-
"@warlock.js/cascade": "5.
|
|
18
|
-
"@warlock.js/core": "5.
|
|
19
|
-
"@warlock.js/logger": "5.
|
|
20
|
-
"@warlock.js/seal": "5.
|
|
15
|
+
"@warlock.js/fs": "5.3.0",
|
|
16
|
+
"@warlock.js/cache": "5.3.0",
|
|
17
|
+
"@warlock.js/cascade": "5.3.0",
|
|
18
|
+
"@warlock.js/core": "5.3.0",
|
|
19
|
+
"@warlock.js/logger": "5.3.0",
|
|
20
|
+
"@warlock.js/seal": "5.3.0"
|
|
21
21
|
},
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"author": "hassanzohdy",
|
|
35
35
|
"license": "MIT",
|
|
36
|
-
"version": "5.
|
|
36
|
+
"version": "5.3.0",
|
|
37
37
|
"type": "module",
|
|
38
38
|
"main": "./esm/index.mjs",
|
|
39
39
|
"module": "./esm/index.mjs",
|
|
@@ -11,9 +11,10 @@ description: 'Run the full login flow via authService.login(Model, credentials,
|
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
13
|
import { authService } from "@warlock.js/auth";
|
|
14
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
14
15
|
import { User } from "@/app/users/models/user.model";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
export const loginController: RequestHandler = async ({ request, response }) => {
|
|
17
18
|
const result = await authService.login(User, {
|
|
18
19
|
email: request.input("email"),
|
|
19
20
|
password: request.input("password"),
|
|
@@ -30,7 +31,7 @@ async function loginController(request: Request, response: Response) {
|
|
|
30
31
|
user: result.user,
|
|
31
32
|
tokens: result.tokens,
|
|
32
33
|
});
|
|
33
|
-
}
|
|
34
|
+
};
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
The returned shape:
|
|
@@ -82,7 +83,9 @@ Useful for "show active sessions" UIs — see `authService.getActiveSessions(use
|
|
|
82
83
|
## Logout — `authService.logout(user, accessToken?, refreshToken?)`
|
|
83
84
|
|
|
84
85
|
```ts
|
|
85
|
-
|
|
86
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
87
|
+
|
|
88
|
+
export const logoutController: RequestHandler = async ({ request, response }) => {
|
|
86
89
|
await authService.logout(
|
|
87
90
|
request.user!,
|
|
88
91
|
request.authorizationValue, // access token from the Authorization header
|
|
@@ -90,7 +93,7 @@ async function logoutController(request: Request, response: Response) {
|
|
|
90
93
|
);
|
|
91
94
|
|
|
92
95
|
return response.success({ message: "Logged out" });
|
|
93
|
-
}
|
|
96
|
+
};
|
|
94
97
|
```
|
|
95
98
|
|
|
96
99
|
The contract:
|
|
@@ -114,7 +117,9 @@ Useful for "logout from all devices" buttons. Fires `token.revoked` per token +
|
|
|
114
117
|
## Refresh tokens — `authService.refreshTokens(refreshTokenString, deviceInfo?)`
|
|
115
118
|
|
|
116
119
|
```ts
|
|
117
|
-
|
|
120
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
121
|
+
|
|
122
|
+
export const refreshController: RequestHandler = async ({ request, response }) => {
|
|
118
123
|
const tokens = await authService.refreshTokens(
|
|
119
124
|
request.input("refreshToken"),
|
|
120
125
|
{ userAgent: request.header("user-agent"), ip: request.ip },
|
|
@@ -125,7 +130,7 @@ async function refreshController(request: Request, response: Response) {
|
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
return response.success({ tokens });
|
|
128
|
-
}
|
|
133
|
+
};
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
Returns a new token pair or `null` (token expired, revoked, or replay-detected). With rotation enabled (default), the old refresh token is consumed; the new pair stays in the same "family." Replay → revoke the whole family. See [`@warlock.js/auth/manage-tokens/SKILL.md`](@warlock.js/auth/manage-tokens/SKILL.md).
|
|
@@ -61,13 +61,15 @@ On failure, the middleware returns one of these 401 responses:
|
|
|
61
61
|
## Reading the user in a controller
|
|
62
62
|
|
|
63
63
|
```ts
|
|
64
|
-
|
|
64
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
65
|
+
|
|
66
|
+
export const accountController: RequestHandler = async ({ request, response }) => {
|
|
65
67
|
const user = request.user!; // typed via your Auth subclass
|
|
66
68
|
return response.success({
|
|
67
69
|
id: user.id,
|
|
68
70
|
email: user.get("email"),
|
|
69
71
|
});
|
|
70
|
-
}
|
|
72
|
+
};
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
Because the middleware always requires a valid token, `request.user` is guaranteed present inside any gated controller (the middleware would have responded 401 otherwise). The `!` is safe here.
|
|
@@ -88,10 +90,12 @@ Every route inside the group is gated — the group's `middleware` array applies
|
|
|
88
90
|
There is no "hydrate `request.user` if a token is present, otherwise continue" mode. `authMiddleware` always requires a valid token. If a route should be reachable anonymously, leave the middleware off — and read the token yourself in the controller if you want soft personalization:
|
|
89
91
|
|
|
90
92
|
```ts
|
|
91
|
-
|
|
93
|
+
import { type RequestHandler } from "@warlock.js/core";
|
|
94
|
+
|
|
95
|
+
export const feedController: RequestHandler = async ({ request, response }) => {
|
|
92
96
|
const token = request.authorizationValue;
|
|
93
97
|
// optionally decode/hydrate manually when a token is present
|
|
94
|
-
}
|
|
98
|
+
};
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
## Custom error responses
|
|
@@ -11,10 +11,10 @@ Two-step on the server: create the user (with hashed password), then issue token
|
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
13
|
import { authService } from "@warlock.js/auth";
|
|
14
|
-
import { hashPassword } from "@warlock.js/core";
|
|
14
|
+
import { hashPassword, type RequestHandler } from "@warlock.js/core";
|
|
15
15
|
import { User } from "@/app/users/models/user.model";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
export const registerController: RequestHandler = async ({ request, response }) => {
|
|
18
18
|
const { email, password, name } = request.all();
|
|
19
19
|
|
|
20
20
|
// 1. Check duplicates
|
|
@@ -41,7 +41,7 @@ async function registerController(request: Request, response: Response) {
|
|
|
41
41
|
user, // shape via static toJsonColumns / static resource
|
|
42
42
|
tokens,
|
|
43
43
|
});
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
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.
|