@meridianjs/auth 0.1.7 → 0.1.9
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/README.md +131 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +35 -0
- package/dist/index.mjs +35 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @meridianjs/auth
|
|
2
|
+
|
|
3
|
+
Authentication module for MeridianJS. Provides JWT-based register/login flows, Google OAuth support, Express middleware for token verification, and RBAC guards.
|
|
4
|
+
|
|
5
|
+
Auto-loaded by `@meridianjs/meridian` — you do not need to add this to `modules[]` yourself.
|
|
6
|
+
|
|
7
|
+
## Service: `authModuleService`
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const svc = req.scope.resolve("authModuleService") as any
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Methods
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// Register a new user — returns { user, token }
|
|
17
|
+
const { user, token } = await svc.register({
|
|
18
|
+
email: "alice@example.com",
|
|
19
|
+
password: "password123",
|
|
20
|
+
first_name: "Alice",
|
|
21
|
+
last_name: "Smith",
|
|
22
|
+
role: "member", // optional: "super-admin" | "admin" | "moderator" | "member"
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Login — returns { user, token }
|
|
26
|
+
const { user, token } = await svc.login({
|
|
27
|
+
email: "alice@example.com",
|
|
28
|
+
password: "password123",
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Verify and decode a JWT
|
|
32
|
+
const payload = await svc.verifyToken(token)
|
|
33
|
+
// payload: { sub, workspaceId, roles, permissions, jti }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Middleware
|
|
37
|
+
|
|
38
|
+
### `authenticateJWT`
|
|
39
|
+
|
|
40
|
+
Verifies the `Authorization: Bearer <token>` header and attaches `req.user`. Returns `401` if the token is missing or invalid.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { authenticateJWT } from "@meridianjs/auth"
|
|
44
|
+
|
|
45
|
+
// Applied globally in middlewares.ts:
|
|
46
|
+
export default {
|
|
47
|
+
routes: [
|
|
48
|
+
{ matcher: "/admin", middlewares: [authenticateJWT] },
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Or inline in a route:
|
|
53
|
+
export const GET = [authenticateJWT, async (req: any, res: Response) => {
|
|
54
|
+
res.json({ userId: req.user.id })
|
|
55
|
+
}]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `requireRoles`
|
|
59
|
+
|
|
60
|
+
Guard a route or handler to specific roles. Returns `403` if the user's roles don't match.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { requireRoles } from "@meridianjs/auth"
|
|
64
|
+
|
|
65
|
+
export const DELETE = [
|
|
66
|
+
authenticateJWT,
|
|
67
|
+
requireRoles("admin", "super-admin"),
|
|
68
|
+
async (req: any, res: Response) => {
|
|
69
|
+
// Only admins reach here
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `requirePermission`
|
|
75
|
+
|
|
76
|
+
Guard based on a specific permission string from a custom AppRole:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { requirePermission } from "@meridianjs/auth"
|
|
80
|
+
|
|
81
|
+
export const POST = [
|
|
82
|
+
authenticateJWT,
|
|
83
|
+
requirePermission("issues:create"),
|
|
84
|
+
async (req: any, res: Response) => { /* ... */ },
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## JWT Payload
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
interface JwtPayload {
|
|
92
|
+
sub: string // User ID
|
|
93
|
+
workspaceId: string // Active workspace ID (null until workspace selected)
|
|
94
|
+
roles: string[] // e.g. ["admin"] or ["member"]
|
|
95
|
+
permissions: string[] // Custom AppRole permissions (e.g. ["issues:create", "issues:delete"])
|
|
96
|
+
jti: string // Unique token ID (for revocation)
|
|
97
|
+
iat: number
|
|
98
|
+
exp: number
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Tokens expire after **7 days** by default. Configure in `projectConfig`:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
projectConfig: {
|
|
106
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
107
|
+
jwtExpiresIn: "30d", // optional
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Google OAuth
|
|
112
|
+
|
|
113
|
+
When `@meridianjs/google-oauth` is configured, the auth module exposes:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
GET /auth/google → Redirects to Google consent screen
|
|
117
|
+
GET /auth/google/callback → Handles OAuth code, returns JWT
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API Routes
|
|
121
|
+
|
|
122
|
+
| Method | Path | Description |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `POST` | `/auth/register` | Register, return `{ user, token }` |
|
|
125
|
+
| `POST` | `/auth/login` | Login, return `{ user, token }` |
|
|
126
|
+
| `GET` | `/auth/invite/:token` | Validate an invitation token |
|
|
127
|
+
| `POST` | `/auth/invite/:token` | Accept invitation (register or login) |
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -61,6 +61,17 @@ declare class AuthModuleService extends AuthModuleService_base {
|
|
|
61
61
|
* 3. Create new user
|
|
62
62
|
*/
|
|
63
63
|
loginOrRegisterWithGoogle(input: GoogleAuthInput): Promise<AuthResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Restore a soft-deleted user via an invite link.
|
|
66
|
+
* Updates their name, password, and role, then issues a fresh session token.
|
|
67
|
+
* The user's ID — and all history tied to it — is preserved.
|
|
68
|
+
*/
|
|
69
|
+
restoreFromInvite(userId: string, input: {
|
|
70
|
+
password: string;
|
|
71
|
+
first_name?: string;
|
|
72
|
+
last_name?: string;
|
|
73
|
+
role?: UserRole;
|
|
74
|
+
}): Promise<AuthResult>;
|
|
64
75
|
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
65
76
|
verifyToken(token: string, secret: string): JwtPayload;
|
|
66
77
|
/** Resolve permissions for a given app_role_id — gracefully degrades if module not loaded. */
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,17 @@ declare class AuthModuleService extends AuthModuleService_base {
|
|
|
61
61
|
* 3. Create new user
|
|
62
62
|
*/
|
|
63
63
|
loginOrRegisterWithGoogle(input: GoogleAuthInput): Promise<AuthResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Restore a soft-deleted user via an invite link.
|
|
66
|
+
* Updates their name, password, and role, then issues a fresh session token.
|
|
67
|
+
* The user's ID — and all history tied to it — is preserved.
|
|
68
|
+
*/
|
|
69
|
+
restoreFromInvite(userId: string, input: {
|
|
70
|
+
password: string;
|
|
71
|
+
first_name?: string;
|
|
72
|
+
last_name?: string;
|
|
73
|
+
role?: UserRole;
|
|
74
|
+
}): Promise<AuthResult>;
|
|
64
75
|
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
65
76
|
verifyToken(token: string, secret: string): JwtPayload;
|
|
66
77
|
/** Resolve permissions for a given app_role_id — gracefully degrades if module not loaded. */
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,9 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
|
|
|
99
99
|
if (!user) {
|
|
100
100
|
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
101
101
|
}
|
|
102
|
+
if (user.deleted_at) {
|
|
103
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
104
|
+
}
|
|
102
105
|
if (!user.is_active) {
|
|
103
106
|
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
104
107
|
}
|
|
@@ -144,6 +147,9 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
if (user) {
|
|
150
|
+
if (user.deleted_at) {
|
|
151
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
152
|
+
}
|
|
147
153
|
if (!user.is_active) {
|
|
148
154
|
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
149
155
|
}
|
|
@@ -201,6 +207,35 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
|
|
|
201
207
|
token
|
|
202
208
|
};
|
|
203
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Restore a soft-deleted user via an invite link.
|
|
212
|
+
* Updates their name, password, and role, then issues a fresh session token.
|
|
213
|
+
* The user's ID — and all history tied to it — is preserved.
|
|
214
|
+
*/
|
|
215
|
+
async restoreFromInvite(userId, input) {
|
|
216
|
+
const userService = this.container.resolve("userModuleService");
|
|
217
|
+
const config = this.container.resolve("config");
|
|
218
|
+
const password_hash = await import_bcrypt.default.hash(input.password, BCRYPT_ROUNDS);
|
|
219
|
+
const user = await userService.restoreUser(userId, {
|
|
220
|
+
password_hash,
|
|
221
|
+
first_name: input.first_name ?? null,
|
|
222
|
+
last_name: input.last_name ?? null,
|
|
223
|
+
role: input.role ?? "member"
|
|
224
|
+
});
|
|
225
|
+
const permissions = await this.resolvePermissions(user.app_role_id);
|
|
226
|
+
const { token, jti, expiresAt } = this.signToken(user.id, null, [user.role], permissions, config.projectConfig.jwtSecret);
|
|
227
|
+
await userService.createSession(jti, user.id, expiresAt).catch(() => {
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
user: {
|
|
231
|
+
id: user.id,
|
|
232
|
+
email: user.email,
|
|
233
|
+
first_name: user.first_name ?? null,
|
|
234
|
+
last_name: user.last_name ?? null
|
|
235
|
+
},
|
|
236
|
+
token
|
|
237
|
+
};
|
|
238
|
+
}
|
|
204
239
|
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
205
240
|
verifyToken(token, secret) {
|
|
206
241
|
return import_jsonwebtoken.default.verify(token, secret, { algorithms: ["HS256"] });
|
package/dist/index.mjs
CHANGED
|
@@ -59,6 +59,9 @@ var AuthModuleService = class extends MeridianService({}) {
|
|
|
59
59
|
if (!user) {
|
|
60
60
|
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
61
61
|
}
|
|
62
|
+
if (user.deleted_at) {
|
|
63
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
64
|
+
}
|
|
62
65
|
if (!user.is_active) {
|
|
63
66
|
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
64
67
|
}
|
|
@@ -104,6 +107,9 @@ var AuthModuleService = class extends MeridianService({}) {
|
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
if (user) {
|
|
110
|
+
if (user.deleted_at) {
|
|
111
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
112
|
+
}
|
|
107
113
|
if (!user.is_active) {
|
|
108
114
|
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
109
115
|
}
|
|
@@ -161,6 +167,35 @@ var AuthModuleService = class extends MeridianService({}) {
|
|
|
161
167
|
token
|
|
162
168
|
};
|
|
163
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Restore a soft-deleted user via an invite link.
|
|
172
|
+
* Updates their name, password, and role, then issues a fresh session token.
|
|
173
|
+
* The user's ID — and all history tied to it — is preserved.
|
|
174
|
+
*/
|
|
175
|
+
async restoreFromInvite(userId, input) {
|
|
176
|
+
const userService = this.container.resolve("userModuleService");
|
|
177
|
+
const config = this.container.resolve("config");
|
|
178
|
+
const password_hash = await bcrypt.hash(input.password, BCRYPT_ROUNDS);
|
|
179
|
+
const user = await userService.restoreUser(userId, {
|
|
180
|
+
password_hash,
|
|
181
|
+
first_name: input.first_name ?? null,
|
|
182
|
+
last_name: input.last_name ?? null,
|
|
183
|
+
role: input.role ?? "member"
|
|
184
|
+
});
|
|
185
|
+
const permissions = await this.resolvePermissions(user.app_role_id);
|
|
186
|
+
const { token, jti, expiresAt } = this.signToken(user.id, null, [user.role], permissions, config.projectConfig.jwtSecret);
|
|
187
|
+
await userService.createSession(jti, user.id, expiresAt).catch(() => {
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
user: {
|
|
191
|
+
id: user.id,
|
|
192
|
+
email: user.email,
|
|
193
|
+
first_name: user.first_name ?? null,
|
|
194
|
+
last_name: user.last_name ?? null
|
|
195
|
+
},
|
|
196
|
+
token
|
|
197
|
+
};
|
|
198
|
+
}
|
|
164
199
|
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
165
200
|
verifyToken(token, secret) {
|
|
166
201
|
return jwt.verify(token, secret, { algorithms: ["HS256"] });
|