@meridianjs/auth 0.1.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/dist/index.d.mts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +179 -0
- package/dist/index.mjs +140 -0
- package/package.json +47 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as _meridianjs_types from '@meridianjs/types';
|
|
2
|
+
import { MeridianContainer } from '@meridianjs/types';
|
|
3
|
+
import { Response, NextFunction } from 'express';
|
|
4
|
+
|
|
5
|
+
interface RegisterInput {
|
|
6
|
+
email: string;
|
|
7
|
+
password: string;
|
|
8
|
+
first_name?: string;
|
|
9
|
+
last_name?: string;
|
|
10
|
+
}
|
|
11
|
+
interface LoginInput {
|
|
12
|
+
email: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
interface AuthResult {
|
|
16
|
+
user: {
|
|
17
|
+
id: string;
|
|
18
|
+
email: string;
|
|
19
|
+
first_name: string | null;
|
|
20
|
+
last_name: string | null;
|
|
21
|
+
};
|
|
22
|
+
token: string;
|
|
23
|
+
}
|
|
24
|
+
interface JwtPayload {
|
|
25
|
+
sub: string;
|
|
26
|
+
workspaceId: string | null;
|
|
27
|
+
roles: string[];
|
|
28
|
+
iat?: number;
|
|
29
|
+
exp?: number;
|
|
30
|
+
}
|
|
31
|
+
declare const AuthModuleService_base: new (container: MeridianContainer) => _meridianjs_types.IModuleService;
|
|
32
|
+
declare class AuthModuleService extends AuthModuleService_base {
|
|
33
|
+
private readonly container;
|
|
34
|
+
constructor(container: MeridianContainer);
|
|
35
|
+
/** Register a new user and return a signed JWT. */
|
|
36
|
+
register(input: RegisterInput): Promise<AuthResult>;
|
|
37
|
+
/** Authenticate with email + password and return a signed JWT. */
|
|
38
|
+
login(input: LoginInput): Promise<AuthResult>;
|
|
39
|
+
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
40
|
+
verifyToken(token: string, secret: string): JwtPayload;
|
|
41
|
+
private signToken;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Express middleware that validates a Bearer JWT on every request.
|
|
46
|
+
*
|
|
47
|
+
* On success, populates req.user = { id, workspaceId, roles } and calls next().
|
|
48
|
+
* On failure, responds 401 Unauthorized.
|
|
49
|
+
*
|
|
50
|
+
* Reads jwtSecret from req.scope (the request-scoped DI container that is
|
|
51
|
+
* attached by the framework before any middleware runs).
|
|
52
|
+
*/
|
|
53
|
+
declare function authenticateJWT(req: any, res: Response, next: NextFunction): void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* RBAC guard — allows the request only if `req.user.roles` contains at least
|
|
57
|
+
* one of the specified roles.
|
|
58
|
+
*
|
|
59
|
+
* Must be used after `authenticateJWT` so that `req.user` is populated.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* { matcher: "/admin/settings", middlewares: [authenticateJWT, requireRoles("admin")] }
|
|
63
|
+
*/
|
|
64
|
+
declare function requireRoles(...roles: string[]): (req: any, res: Response, next: NextFunction) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Workspace isolation guard — rejects requests where the `workspace_id` query
|
|
67
|
+
* param or body field does not match the authenticated user's workspace.
|
|
68
|
+
*
|
|
69
|
+
* Allows the request through when no `workspace_id` is present (so general
|
|
70
|
+
* listing endpoints that omit workspace_id are not blocked).
|
|
71
|
+
*
|
|
72
|
+
* Must be used after `authenticateJWT`.
|
|
73
|
+
*/
|
|
74
|
+
declare function requireWorkspace(req: any, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
|
|
75
|
+
|
|
76
|
+
declare const AUTH_MODULE = "authModuleService";
|
|
77
|
+
declare const _default: _meridianjs_types.ModuleDefinition;
|
|
78
|
+
|
|
79
|
+
export { AUTH_MODULE, AuthModuleService, type AuthResult, type JwtPayload, type LoginInput, type RegisterInput, authenticateJWT, _default as default, requireRoles, requireWorkspace };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as _meridianjs_types from '@meridianjs/types';
|
|
2
|
+
import { MeridianContainer } from '@meridianjs/types';
|
|
3
|
+
import { Response, NextFunction } from 'express';
|
|
4
|
+
|
|
5
|
+
interface RegisterInput {
|
|
6
|
+
email: string;
|
|
7
|
+
password: string;
|
|
8
|
+
first_name?: string;
|
|
9
|
+
last_name?: string;
|
|
10
|
+
}
|
|
11
|
+
interface LoginInput {
|
|
12
|
+
email: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
interface AuthResult {
|
|
16
|
+
user: {
|
|
17
|
+
id: string;
|
|
18
|
+
email: string;
|
|
19
|
+
first_name: string | null;
|
|
20
|
+
last_name: string | null;
|
|
21
|
+
};
|
|
22
|
+
token: string;
|
|
23
|
+
}
|
|
24
|
+
interface JwtPayload {
|
|
25
|
+
sub: string;
|
|
26
|
+
workspaceId: string | null;
|
|
27
|
+
roles: string[];
|
|
28
|
+
iat?: number;
|
|
29
|
+
exp?: number;
|
|
30
|
+
}
|
|
31
|
+
declare const AuthModuleService_base: new (container: MeridianContainer) => _meridianjs_types.IModuleService;
|
|
32
|
+
declare class AuthModuleService extends AuthModuleService_base {
|
|
33
|
+
private readonly container;
|
|
34
|
+
constructor(container: MeridianContainer);
|
|
35
|
+
/** Register a new user and return a signed JWT. */
|
|
36
|
+
register(input: RegisterInput): Promise<AuthResult>;
|
|
37
|
+
/** Authenticate with email + password and return a signed JWT. */
|
|
38
|
+
login(input: LoginInput): Promise<AuthResult>;
|
|
39
|
+
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
40
|
+
verifyToken(token: string, secret: string): JwtPayload;
|
|
41
|
+
private signToken;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Express middleware that validates a Bearer JWT on every request.
|
|
46
|
+
*
|
|
47
|
+
* On success, populates req.user = { id, workspaceId, roles } and calls next().
|
|
48
|
+
* On failure, responds 401 Unauthorized.
|
|
49
|
+
*
|
|
50
|
+
* Reads jwtSecret from req.scope (the request-scoped DI container that is
|
|
51
|
+
* attached by the framework before any middleware runs).
|
|
52
|
+
*/
|
|
53
|
+
declare function authenticateJWT(req: any, res: Response, next: NextFunction): void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* RBAC guard — allows the request only if `req.user.roles` contains at least
|
|
57
|
+
* one of the specified roles.
|
|
58
|
+
*
|
|
59
|
+
* Must be used after `authenticateJWT` so that `req.user` is populated.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* { matcher: "/admin/settings", middlewares: [authenticateJWT, requireRoles("admin")] }
|
|
63
|
+
*/
|
|
64
|
+
declare function requireRoles(...roles: string[]): (req: any, res: Response, next: NextFunction) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Workspace isolation guard — rejects requests where the `workspace_id` query
|
|
67
|
+
* param or body field does not match the authenticated user's workspace.
|
|
68
|
+
*
|
|
69
|
+
* Allows the request through when no `workspace_id` is present (so general
|
|
70
|
+
* listing endpoints that omit workspace_id are not blocked).
|
|
71
|
+
*
|
|
72
|
+
* Must be used after `authenticateJWT`.
|
|
73
|
+
*/
|
|
74
|
+
declare function requireWorkspace(req: any, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
|
|
75
|
+
|
|
76
|
+
declare const AUTH_MODULE = "authModuleService";
|
|
77
|
+
declare const _default: _meridianjs_types.ModuleDefinition;
|
|
78
|
+
|
|
79
|
+
export { AUTH_MODULE, AuthModuleService, type AuthResult, type JwtPayload, type LoginInput, type RegisterInput, authenticateJWT, _default as default, requireRoles, requireWorkspace };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AUTH_MODULE: () => AUTH_MODULE,
|
|
34
|
+
AuthModuleService: () => AuthModuleService,
|
|
35
|
+
authenticateJWT: () => authenticateJWT,
|
|
36
|
+
default: () => index_default,
|
|
37
|
+
requireRoles: () => requireRoles,
|
|
38
|
+
requireWorkspace: () => requireWorkspace
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
var import_framework_utils2 = require("@meridianjs/framework-utils");
|
|
42
|
+
|
|
43
|
+
// src/service.ts
|
|
44
|
+
var import_framework_utils = require("@meridianjs/framework-utils");
|
|
45
|
+
var import_bcrypt = __toESM(require("bcrypt"));
|
|
46
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
47
|
+
var BCRYPT_ROUNDS = 12;
|
|
48
|
+
var JWT_EXPIRES_IN = "7d";
|
|
49
|
+
var AuthModuleService = class extends (0, import_framework_utils.MeridianService)({}) {
|
|
50
|
+
container;
|
|
51
|
+
constructor(container) {
|
|
52
|
+
super(container);
|
|
53
|
+
this.container = container;
|
|
54
|
+
}
|
|
55
|
+
/** Register a new user and return a signed JWT. */
|
|
56
|
+
async register(input) {
|
|
57
|
+
const userService = this.container.resolve("userModuleService");
|
|
58
|
+
const config = this.container.resolve("config");
|
|
59
|
+
const existing = await userService.retrieveUserByEmail(input.email);
|
|
60
|
+
if (existing) {
|
|
61
|
+
throw Object.assign(new Error("Email already registered"), { status: 409 });
|
|
62
|
+
}
|
|
63
|
+
const password_hash = await import_bcrypt.default.hash(input.password, BCRYPT_ROUNDS);
|
|
64
|
+
const user = await userService.createUser({
|
|
65
|
+
email: input.email.toLowerCase().trim(),
|
|
66
|
+
password_hash,
|
|
67
|
+
first_name: input.first_name ?? null,
|
|
68
|
+
last_name: input.last_name ?? null,
|
|
69
|
+
is_active: true
|
|
70
|
+
});
|
|
71
|
+
const token = this.signToken(user.id, null, [], config.projectConfig.jwtSecret);
|
|
72
|
+
return {
|
|
73
|
+
user: {
|
|
74
|
+
id: user.id,
|
|
75
|
+
email: user.email,
|
|
76
|
+
first_name: user.first_name ?? null,
|
|
77
|
+
last_name: user.last_name ?? null
|
|
78
|
+
},
|
|
79
|
+
token
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Authenticate with email + password and return a signed JWT. */
|
|
83
|
+
async login(input) {
|
|
84
|
+
const userService = this.container.resolve("userModuleService");
|
|
85
|
+
const config = this.container.resolve("config");
|
|
86
|
+
const user = await userService.retrieveUserByEmail(input.email.toLowerCase().trim());
|
|
87
|
+
if (!user) {
|
|
88
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
89
|
+
}
|
|
90
|
+
if (!user.is_active) {
|
|
91
|
+
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
92
|
+
}
|
|
93
|
+
const valid = await import_bcrypt.default.compare(input.password, user.password_hash);
|
|
94
|
+
if (!valid) {
|
|
95
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
96
|
+
}
|
|
97
|
+
await userService.recordLogin(user.id).catch(() => {
|
|
98
|
+
});
|
|
99
|
+
const token = this.signToken(user.id, null, [], config.projectConfig.jwtSecret);
|
|
100
|
+
return {
|
|
101
|
+
user: {
|
|
102
|
+
id: user.id,
|
|
103
|
+
email: user.email,
|
|
104
|
+
first_name: user.first_name ?? null,
|
|
105
|
+
last_name: user.last_name ?? null
|
|
106
|
+
},
|
|
107
|
+
token
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
111
|
+
verifyToken(token, secret) {
|
|
112
|
+
return import_jsonwebtoken.default.verify(token, secret);
|
|
113
|
+
}
|
|
114
|
+
signToken(userId, workspaceId, roles, secret) {
|
|
115
|
+
return import_jsonwebtoken.default.sign({ sub: userId, workspaceId, roles }, secret, {
|
|
116
|
+
expiresIn: JWT_EXPIRES_IN
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/middleware.ts
|
|
122
|
+
var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"));
|
|
123
|
+
function authenticateJWT(req, res, next) {
|
|
124
|
+
const authHeader = req.headers.authorization;
|
|
125
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
126
|
+
res.status(401).json({ error: { message: "Unauthorized \u2014 Bearer token required" } });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const token = authHeader.substring(7);
|
|
130
|
+
let config;
|
|
131
|
+
try {
|
|
132
|
+
const scope = req.scope;
|
|
133
|
+
config = scope.resolve("config");
|
|
134
|
+
} catch {
|
|
135
|
+
res.status(500).json({ error: { message: "Server misconfiguration" } });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const payload = import_jsonwebtoken2.default.verify(token, config.projectConfig.jwtSecret);
|
|
140
|
+
req.user = {
|
|
141
|
+
id: payload.sub,
|
|
142
|
+
workspaceId: payload.workspaceId ?? null,
|
|
143
|
+
roles: Array.isArray(payload.roles) ? payload.roles : []
|
|
144
|
+
};
|
|
145
|
+
next();
|
|
146
|
+
} catch {
|
|
147
|
+
res.status(401).json({ error: { message: "Invalid or expired token" } });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/guards.ts
|
|
152
|
+
function requireRoles(...roles) {
|
|
153
|
+
return (req, res, next) => {
|
|
154
|
+
const userRoles = req.user?.roles ?? [];
|
|
155
|
+
if (roles.some((r) => userRoles.includes(r))) return next();
|
|
156
|
+
res.status(403).json({ error: { message: "Forbidden" } });
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function requireWorkspace(req, res, next) {
|
|
160
|
+
const workspaceId = req.query?.workspace_id ?? req.body?.workspace_id;
|
|
161
|
+
if (workspaceId && req.user?.workspaceId && req.user.workspaceId !== workspaceId) {
|
|
162
|
+
return res.status(403).json({ error: { message: "Forbidden \u2014 wrong workspace" } });
|
|
163
|
+
}
|
|
164
|
+
next();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/index.ts
|
|
168
|
+
var AUTH_MODULE = "authModuleService";
|
|
169
|
+
var index_default = (0, import_framework_utils2.Module)(AUTH_MODULE, {
|
|
170
|
+
service: AuthModuleService
|
|
171
|
+
});
|
|
172
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
173
|
+
0 && (module.exports = {
|
|
174
|
+
AUTH_MODULE,
|
|
175
|
+
AuthModuleService,
|
|
176
|
+
authenticateJWT,
|
|
177
|
+
requireRoles,
|
|
178
|
+
requireWorkspace
|
|
179
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Module } from "@meridianjs/framework-utils";
|
|
3
|
+
|
|
4
|
+
// src/service.ts
|
|
5
|
+
import { MeridianService } from "@meridianjs/framework-utils";
|
|
6
|
+
import bcrypt from "bcrypt";
|
|
7
|
+
import jwt from "jsonwebtoken";
|
|
8
|
+
var BCRYPT_ROUNDS = 12;
|
|
9
|
+
var JWT_EXPIRES_IN = "7d";
|
|
10
|
+
var AuthModuleService = class extends MeridianService({}) {
|
|
11
|
+
container;
|
|
12
|
+
constructor(container) {
|
|
13
|
+
super(container);
|
|
14
|
+
this.container = container;
|
|
15
|
+
}
|
|
16
|
+
/** Register a new user and return a signed JWT. */
|
|
17
|
+
async register(input) {
|
|
18
|
+
const userService = this.container.resolve("userModuleService");
|
|
19
|
+
const config = this.container.resolve("config");
|
|
20
|
+
const existing = await userService.retrieveUserByEmail(input.email);
|
|
21
|
+
if (existing) {
|
|
22
|
+
throw Object.assign(new Error("Email already registered"), { status: 409 });
|
|
23
|
+
}
|
|
24
|
+
const password_hash = await bcrypt.hash(input.password, BCRYPT_ROUNDS);
|
|
25
|
+
const user = await userService.createUser({
|
|
26
|
+
email: input.email.toLowerCase().trim(),
|
|
27
|
+
password_hash,
|
|
28
|
+
first_name: input.first_name ?? null,
|
|
29
|
+
last_name: input.last_name ?? null,
|
|
30
|
+
is_active: true
|
|
31
|
+
});
|
|
32
|
+
const token = this.signToken(user.id, null, [], config.projectConfig.jwtSecret);
|
|
33
|
+
return {
|
|
34
|
+
user: {
|
|
35
|
+
id: user.id,
|
|
36
|
+
email: user.email,
|
|
37
|
+
first_name: user.first_name ?? null,
|
|
38
|
+
last_name: user.last_name ?? null
|
|
39
|
+
},
|
|
40
|
+
token
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Authenticate with email + password and return a signed JWT. */
|
|
44
|
+
async login(input) {
|
|
45
|
+
const userService = this.container.resolve("userModuleService");
|
|
46
|
+
const config = this.container.resolve("config");
|
|
47
|
+
const user = await userService.retrieveUserByEmail(input.email.toLowerCase().trim());
|
|
48
|
+
if (!user) {
|
|
49
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
50
|
+
}
|
|
51
|
+
if (!user.is_active) {
|
|
52
|
+
throw Object.assign(new Error("Account deactivated"), { status: 403 });
|
|
53
|
+
}
|
|
54
|
+
const valid = await bcrypt.compare(input.password, user.password_hash);
|
|
55
|
+
if (!valid) {
|
|
56
|
+
throw Object.assign(new Error("Invalid credentials"), { status: 401 });
|
|
57
|
+
}
|
|
58
|
+
await userService.recordLogin(user.id).catch(() => {
|
|
59
|
+
});
|
|
60
|
+
const token = this.signToken(user.id, null, [], config.projectConfig.jwtSecret);
|
|
61
|
+
return {
|
|
62
|
+
user: {
|
|
63
|
+
id: user.id,
|
|
64
|
+
email: user.email,
|
|
65
|
+
first_name: user.first_name ?? null,
|
|
66
|
+
last_name: user.last_name ?? null
|
|
67
|
+
},
|
|
68
|
+
token
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
|
|
72
|
+
verifyToken(token, secret) {
|
|
73
|
+
return jwt.verify(token, secret);
|
|
74
|
+
}
|
|
75
|
+
signToken(userId, workspaceId, roles, secret) {
|
|
76
|
+
return jwt.sign({ sub: userId, workspaceId, roles }, secret, {
|
|
77
|
+
expiresIn: JWT_EXPIRES_IN
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/middleware.ts
|
|
83
|
+
import jwt2 from "jsonwebtoken";
|
|
84
|
+
function authenticateJWT(req, res, next) {
|
|
85
|
+
const authHeader = req.headers.authorization;
|
|
86
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
87
|
+
res.status(401).json({ error: { message: "Unauthorized \u2014 Bearer token required" } });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const token = authHeader.substring(7);
|
|
91
|
+
let config;
|
|
92
|
+
try {
|
|
93
|
+
const scope = req.scope;
|
|
94
|
+
config = scope.resolve("config");
|
|
95
|
+
} catch {
|
|
96
|
+
res.status(500).json({ error: { message: "Server misconfiguration" } });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const payload = jwt2.verify(token, config.projectConfig.jwtSecret);
|
|
101
|
+
req.user = {
|
|
102
|
+
id: payload.sub,
|
|
103
|
+
workspaceId: payload.workspaceId ?? null,
|
|
104
|
+
roles: Array.isArray(payload.roles) ? payload.roles : []
|
|
105
|
+
};
|
|
106
|
+
next();
|
|
107
|
+
} catch {
|
|
108
|
+
res.status(401).json({ error: { message: "Invalid or expired token" } });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/guards.ts
|
|
113
|
+
function requireRoles(...roles) {
|
|
114
|
+
return (req, res, next) => {
|
|
115
|
+
const userRoles = req.user?.roles ?? [];
|
|
116
|
+
if (roles.some((r) => userRoles.includes(r))) return next();
|
|
117
|
+
res.status(403).json({ error: { message: "Forbidden" } });
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function requireWorkspace(req, res, next) {
|
|
121
|
+
const workspaceId = req.query?.workspace_id ?? req.body?.workspace_id;
|
|
122
|
+
if (workspaceId && req.user?.workspaceId && req.user.workspaceId !== workspaceId) {
|
|
123
|
+
return res.status(403).json({ error: { message: "Forbidden \u2014 wrong workspace" } });
|
|
124
|
+
}
|
|
125
|
+
next();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/index.ts
|
|
129
|
+
var AUTH_MODULE = "authModuleService";
|
|
130
|
+
var index_default = Module(AUTH_MODULE, {
|
|
131
|
+
service: AuthModuleService
|
|
132
|
+
});
|
|
133
|
+
export {
|
|
134
|
+
AUTH_MODULE,
|
|
135
|
+
AuthModuleService,
|
|
136
|
+
authenticateJWT,
|
|
137
|
+
index_default as default,
|
|
138
|
+
requireRoles,
|
|
139
|
+
requireWorkspace
|
|
140
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meridianjs/auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Meridian auth module — JWT authentication and middleware",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"clean": "rm -rf dist",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@meridianjs/types": "^0.1.0",
|
|
30
|
+
"@meridianjs/framework-utils": "^0.1.0",
|
|
31
|
+
"jsonwebtoken": "^9.0.2",
|
|
32
|
+
"bcrypt": "^5.1.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
36
|
+
"@types/bcrypt": "^5.0.2",
|
|
37
|
+
"@types/express": "^5.0.0",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "*"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|