@warlock.js/auth 5.19.1 → 5.20.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,168 +1,168 @@
1
- ---
2
- name: customize-user-type
3
- description: "Customize user type (multi-user-type auth) in @warlock.js/auth; use when you need to customize user type."
4
- ---
5
-
6
- # Customize user type (multi-user-type auth)
7
-
8
- The `Auth` base class has a `userType` slot. Subclass it once per type, register each class under `config.auth.userType.<slug>`, and the auth flow handles the rest.
9
-
10
- ## Define a model per user type
11
-
12
- ```ts title="src/app/users/models/user/user.model.ts"
13
- import { Auth } from "@warlock.js/auth";
14
- import { RegisterModel } from "@warlock.js/cascade";
15
- import { v, type Infer } from "@warlock.js/seal";
16
-
17
- export const userSchema = v.object({
18
- email: v.string().email(),
19
- password: v.string(),
20
- });
21
-
22
- export type UserSchema = Infer<typeof userSchema>;
23
-
24
- @RegisterModel()
25
- export class User extends Auth<UserSchema> {
26
- public static table = "users";
27
- public static schema = userSchema;
28
-
29
- public get userType(): string {
30
- return "user";
31
- }
32
- }
33
- ```
34
-
35
- ```ts title="src/app/admins/models/admin.model.ts"
36
- import { Auth } from "@warlock.js/auth";
37
- import { RegisterModel } from "@warlock.js/cascade";
38
- import { v, type Infer } from "@warlock.js/seal";
39
-
40
- export const adminSchema = v.object({
41
- email: v.string().email(),
42
- password: v.string(),
43
- });
44
-
45
- export type AdminSchema = Infer<typeof adminSchema>;
46
-
47
- @RegisterModel()
48
- export class Admin extends Auth<AdminSchema> {
49
- public static table = "admins";
50
- public static schema = adminSchema;
51
-
52
- public get userType(): string {
53
- return "admin";
54
- }
55
- }
56
- ```
57
-
58
- Each gets its own table, its own schema, its own `userType` slug. They DON'T share table — they're separate models.
59
-
60
- ## Register them in `config.auth`
61
-
62
- ```ts title="src/config/auth.ts"
63
- import { env } from "@warlock.js/core";
64
- import { User } from "app/users/models/user/user.model";
65
- import { Admin } from "app/admins/models/admin.model";
66
-
67
- export default {
68
- userType: {
69
- user: User,
70
- admin: Admin,
71
- // staff: Staff,
72
- // client: Client,
73
- },
74
- accessToken: {
75
- secret: env("JWT_SECRET"),
76
- expiresIn: "1h",
77
- },
78
- refreshToken: {
79
- secret: env("JWT_REFRESH_SECRET"),
80
- enabled: true,
81
- expiresIn: "30d",
82
- rotation: true,
83
- },
84
- };
85
- ```
86
-
87
- The keys (`"user"`, `"admin"`) are the **userType slugs** that flow through every token, middleware call, and event payload.
88
-
89
- ## Gate routes per user type
90
-
91
- ```ts
92
- import { router, type RequestHandler } from "@warlock.js/core";
93
- import { authMiddleware } from "@warlock.js/auth";
94
-
95
- const userAccountController: RequestHandler = async ({ response }) => response.success({});
96
- const listUsersController: RequestHandler = async ({ response }) => response.success({});
97
- const backOfficeController: RequestHandler = async ({ response }) => response.success({});
98
- const dashboardController: RequestHandler = async ({ response }) => response.success({});
99
-
100
- router.get("/account", userAccountController, { middleware: [authMiddleware("user")] });
101
- router.get("/admin/users", listUsersController, { middleware: [authMiddleware("admin")] });
102
- router.get("/back-office", backOfficeController, { middleware: [authMiddleware(["admin", "staff"])] });
103
- router.get("/dashboard", dashboardController, { middleware: [authMiddleware([])] }); // any logged-in
104
- ```
105
-
106
- See [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md).
107
-
108
- ## Login per user type — pass the right Model
109
-
110
- `authService.login(Model, credentials, deviceInfo?)` is keyed off the model you pass:
111
-
112
- ```ts
113
- // User login endpoint
114
- const result = await authService.login(User, credentials);
115
- // Issues tokens with userType "user"; middleware will route them to User model.
116
-
117
- // Admin login endpoint
118
- const result = await authService.login(Admin, credentials);
119
- // Issues tokens with userType "admin"; middleware will route them to Admin model.
120
- ```
121
-
122
- The middleware then uses `config.auth.userType[token.userType]` to know which model to hydrate.
123
-
124
- ## Cross-type behavior
125
-
126
- - **Tokens are scoped to their issuing user-type.** A user-type token doesn't unlock admin-type routes.
127
- - **AccessToken / RefreshToken rows carry the `user_type` column.** Same model classes, different rows per type.
128
- - **`authMiddleware(["admin", "user"])`** allows either — useful for endpoints shared between roles.
129
-
130
- ## When NOT to use multi-user-type
131
-
132
- If the distinction is **permissions/roles within one user shape**, use a `role` column on a single User model instead. Multi-user-type is right when:
133
-
134
- - Different tables / schemas (admins have an `admin_level`; users have a `subscription_tier`).
135
- - Separate registration flows (admins are created via an admin panel; users self-register).
136
- - Truly separate concepts at the data layer (clients vs vendors in a marketplace).
137
-
138
- If users and admins differ only in a `role` field, stick with one `User` model + a role check at the controller layer.
139
-
140
- ## `Auth` base — what your subclass inherits
141
-
142
- ```ts
143
- abstract class Auth<TSchema> extends Model<TSchema> implements Authenticable {
144
- // ...all the Model<> methods
145
- public abstract get userType(): string;
146
- public generateAccessToken(payload?: Record<string, unknown>): Promise<AccessTokenOutput>;
147
- public generateRefreshToken(deviceInfo?: DeviceInfo): Promise<RefreshToken | undefined>;
148
- public createTokenPair(deviceInfo?: DeviceInfo): Promise<TokenPair>;
149
- public confirmPassword(password: string): Promise<boolean>;
150
- }
151
- ```
152
-
153
- `userType` is the only required override (an abstract getter — return the type slug). Override `generateAccessToken` if you need a non-default payload.
154
-
155
- `Auth` implements the `Authenticable` contract — that interface mirrors exactly these methods (`userType`, `generateAccessToken`, `generateRefreshToken`, `createTokenPair`, `confirmPassword`), so the class fails to compile if it drifts from the contract. Use `confirmPassword(plaintext)` to check a password against the stored hash (e.g. a "confirm current password" step).
156
-
157
- ## Things NOT to do
158
-
159
- - Don't use multi-user-type for what's really role-based access control. Use a `role` column on a single User model when the data shape is shared.
160
- - Don't forget the `public get userType(): string` override. It's an abstract getter on `Auth` — a subclass without it won't compile, and middleware lookups key off its return value.
161
- - Don't reuse the same `userType` slug across two models — the `config.auth.userType` map can only point one slug at one model.
162
- - Don't put admins and users in the same table differentiated by a flag. Separate tables means migrations don't coupling, queries don't accidentally cross, and audit logs are cleaner.
163
-
164
- ## See also
165
-
166
- - [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md) — `authMiddleware` semantics
167
- - [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — passing the right Model to `login`
168
- - [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md) — `@RegisterModel`, models in general
1
+ ---
2
+ name: customize-user-type
3
+ 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`.'
4
+ ---
5
+
6
+ # Customize user type (multi-user-type auth)
7
+
8
+ The `Auth` base class has a `userType` slot. Subclass it once per type, register each class under `config.auth.userType.<slug>`, and the auth flow handles the rest.
9
+
10
+ ## Define a model per user type
11
+
12
+ ```ts title="src/app/users/models/user/user.model.ts"
13
+ import { Auth } from "@warlock.js/auth";
14
+ import { RegisterModel } from "@warlock.js/cascade";
15
+ import { v, type Infer } from "@warlock.js/seal";
16
+
17
+ export const userSchema = v.object({
18
+ email: v.string().email(),
19
+ password: v.string(),
20
+ });
21
+
22
+ export type UserSchema = Infer<typeof userSchema>;
23
+
24
+ @RegisterModel()
25
+ export class User extends Auth<UserSchema> {
26
+ public static table = "users";
27
+ public static schema = userSchema;
28
+
29
+ public get userType(): string {
30
+ return "user";
31
+ }
32
+ }
33
+ ```
34
+
35
+ ```ts title="src/app/admins/models/admin.model.ts"
36
+ import { Auth } from "@warlock.js/auth";
37
+ import { RegisterModel } from "@warlock.js/cascade";
38
+ import { v, type Infer } from "@warlock.js/seal";
39
+
40
+ export const adminSchema = v.object({
41
+ email: v.string().email(),
42
+ password: v.string(),
43
+ });
44
+
45
+ export type AdminSchema = Infer<typeof adminSchema>;
46
+
47
+ @RegisterModel()
48
+ export class Admin extends Auth<AdminSchema> {
49
+ public static table = "admins";
50
+ public static schema = adminSchema;
51
+
52
+ public get userType(): string {
53
+ return "admin";
54
+ }
55
+ }
56
+ ```
57
+
58
+ Each gets its own table, its own schema, its own `userType` slug. They DON'T share table — they're separate models.
59
+
60
+ ## Register them in `config.auth`
61
+
62
+ ```ts title="src/config/auth.ts"
63
+ import { env } from "@warlock.js/core";
64
+ import { User } from "app/users/models/user/user.model";
65
+ import { Admin } from "app/admins/models/admin.model";
66
+
67
+ export default {
68
+ userType: {
69
+ user: User,
70
+ admin: Admin,
71
+ // staff: Staff,
72
+ // client: Client,
73
+ },
74
+ accessToken: {
75
+ secret: env("JWT_SECRET"),
76
+ expiresIn: "1h",
77
+ },
78
+ refreshToken: {
79
+ secret: env("JWT_REFRESH_SECRET"),
80
+ enabled: true,
81
+ expiresIn: "30d",
82
+ rotation: true,
83
+ },
84
+ };
85
+ ```
86
+
87
+ The keys (`"user"`, `"admin"`) are the **userType slugs** that flow through every token, middleware call, and event payload.
88
+
89
+ ## Gate routes per user type
90
+
91
+ ```ts
92
+ import { router, type RequestHandler } from "@warlock.js/core";
93
+ import { authMiddleware } from "@warlock.js/auth";
94
+
95
+ const userAccountController: RequestHandler = async ({ response }) => response.success({});
96
+ const listUsersController: RequestHandler = async ({ response }) => response.success({});
97
+ const backOfficeController: RequestHandler = async ({ response }) => response.success({});
98
+ const dashboardController: RequestHandler = async ({ response }) => response.success({});
99
+
100
+ router.get("/account", userAccountController, { middleware: [authMiddleware("user")] });
101
+ router.get("/admin/users", listUsersController, { middleware: [authMiddleware("admin")] });
102
+ router.get("/back-office", backOfficeController, { middleware: [authMiddleware(["admin", "staff"])] });
103
+ router.get("/dashboard", dashboardController, { middleware: [authMiddleware([])] }); // any logged-in
104
+ ```
105
+
106
+ See [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md).
107
+
108
+ ## Login per user type — pass the right Model
109
+
110
+ `authService.login(Model, credentials, deviceInfo?)` is keyed off the model you pass:
111
+
112
+ ```ts
113
+ // User login endpoint
114
+ const result = await authService.login(User, credentials);
115
+ // Issues tokens with userType "user"; middleware will route them to User model.
116
+
117
+ // Admin login endpoint
118
+ const result = await authService.login(Admin, credentials);
119
+ // Issues tokens with userType "admin"; middleware will route them to Admin model.
120
+ ```
121
+
122
+ The middleware then uses `config.auth.userType[token.userType]` to know which model to hydrate.
123
+
124
+ ## Cross-type behavior
125
+
126
+ - **Tokens are scoped to their issuing user-type.** A user-type token doesn't unlock admin-type routes.
127
+ - **AccessToken / RefreshToken rows carry the `user_type` column.** Same model classes, different rows per type.
128
+ - **`authMiddleware(["admin", "user"])`** allows either — useful for endpoints shared between roles.
129
+
130
+ ## When NOT to use multi-user-type
131
+
132
+ If the distinction is **permissions/roles within one user shape**, use a `role` column on a single User model instead. Multi-user-type is right when:
133
+
134
+ - Different tables / schemas (admins have an `admin_level`; users have a `subscription_tier`).
135
+ - Separate registration flows (admins are created via an admin panel; users self-register).
136
+ - Truly separate concepts at the data layer (clients vs vendors in a marketplace).
137
+
138
+ If users and admins differ only in a `role` field, stick with one `User` model + a role check at the controller layer.
139
+
140
+ ## `Auth` base — what your subclass inherits
141
+
142
+ ```ts
143
+ abstract class Auth<TSchema> extends Model<TSchema> implements Authenticable {
144
+ // ...all the Model<> methods
145
+ public abstract get userType(): string;
146
+ public generateAccessToken(payload?: Record<string, unknown>): Promise<AccessTokenOutput>;
147
+ public generateRefreshToken(deviceInfo?: DeviceInfo): Promise<RefreshToken | undefined>;
148
+ public createTokenPair(deviceInfo?: DeviceInfo): Promise<TokenPair>;
149
+ public confirmPassword(password: string): Promise<boolean>;
150
+ }
151
+ ```
152
+
153
+ `userType` is the only required override (an abstract getter — return the type slug). Override `generateAccessToken` if you need a non-default payload.
154
+
155
+ `Auth` implements the `Authenticable` contract — that interface mirrors exactly these methods (`userType`, `generateAccessToken`, `generateRefreshToken`, `createTokenPair`, `confirmPassword`), so the class fails to compile if it drifts from the contract. Use `confirmPassword(plaintext)` to check a password against the stored hash (e.g. a "confirm current password" step).
156
+
157
+ ## Things NOT to do
158
+
159
+ - Don't use multi-user-type for what's really role-based access control. Use a `role` column on a single User model when the data shape is shared.
160
+ - Don't forget the `public get userType(): string` override. It's an abstract getter on `Auth` — a subclass without it won't compile, and middleware lookups key off its return value.
161
+ - Don't reuse the same `userType` slug across two models — the `config.auth.userType` map can only point one slug at one model.
162
+ - Don't put admins and users in the same table differentiated by a flag. Separate tables means migrations don't coupling, queries don't accidentally cross, and audit logs are cleaner.
163
+
164
+ ## See also
165
+
166
+ - [`@warlock.js/auth/protect-routes/SKILL.md`](@warlock.js/auth/protect-routes/SKILL.md) — `authMiddleware` semantics
167
+ - [`@warlock.js/auth/handle-login-and-logout/SKILL.md`](@warlock.js/auth/handle-login-and-logout/SKILL.md) — passing the right Model to `login`
168
+ - [`@warlock.js/cascade/define-model/SKILL.md`](@warlock.js/cascade/define-model/SKILL.md) — `@RegisterModel`, models in general