@loomcore/api 0.1.38 → 0.1.39
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 +1 -1
- package/dist/__tests__/common-test.utils.js +2 -11
- package/dist/controllers/api.controller.js +10 -10
- package/dist/controllers/auth.controller.js +4 -4
- package/dist/controllers/organizations.controller.js +3 -3
- package/dist/controllers/users.controller.js +9 -9
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/is-authorized.d.ts +3 -0
- package/dist/middleware/is-authorized.js +40 -0
- package/package.json +1 -1
- package/dist/middleware/is-authenticated.d.ts +0 -2
- package/dist/middleware/is-authenticated.js +0 -27
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This library provides a foundational structure for creating robust APIs, with a
|
|
|
8
8
|
|
|
9
9
|
- **Generic API Controller**: A base `ApiController` that automatically scaffolds a full suite of RESTful endpoints for any data model. This includes support for CRUD operations, batch updates, pagination, and counting.
|
|
10
10
|
- **Built-in Multi-tenancy**: Optional, configuration-driven multi-tenancy. When enabled, all database operations are automatically scoped to the current user's organization, ensuring strict data isolation.
|
|
11
|
-
- **Authentication & Authorization**: Integrated JWT-based authentication middleware (`
|
|
11
|
+
- **Authentication & Authorization**: Integrated JWT-based authentication middleware (`isAuthorized`) to easily secure endpoints and support for OAuth 2.0 Code Flow, including the use of refresh tokens.
|
|
12
12
|
- **User & Organization Management**: Pre-built services and controllers for common user and organization management tasks.
|
|
13
13
|
- **Password Management**: Includes services for handling password reset requests and workflows.
|
|
14
14
|
- **Email Service Integration**: A ready-to-use service for sending transactional emails (e.g., for password resets or welcome messages).
|
|
@@ -131,17 +131,8 @@ async function simulateloginWithTestUser() {
|
|
|
131
131
|
return `Bearer ${loginResponse.tokens.accessToken}`;
|
|
132
132
|
}
|
|
133
133
|
function getAuthToken() {
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const payload = {
|
|
137
|
-
user: {
|
|
138
|
-
_id: metaOrgUser._id,
|
|
139
|
-
email: metaOrgUser.email
|
|
140
|
-
},
|
|
141
|
-
organization: metaOrg,
|
|
142
|
-
_orgId: metaOrgUser._orgId
|
|
143
|
-
};
|
|
144
|
-
const token = JwtService.sign(payload, JWT_SECRET, { expiresIn: 3600 });
|
|
134
|
+
const userContext = getTestMetaOrgUserContext();
|
|
135
|
+
const token = JwtService.sign(userContext, JWT_SECRET, { expiresIn: 3600 });
|
|
145
136
|
return `Bearer ${token}`;
|
|
146
137
|
}
|
|
147
138
|
function verifyToken(token) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BadRequestError } from '../errors/index.js';
|
|
2
2
|
import { entityUtils } from '@loomcore/common/utils';
|
|
3
|
-
import { isAuthenticated } from '../middleware/index.js';
|
|
4
3
|
import { apiUtils } from '../utils/index.js';
|
|
4
|
+
import { isAuthorized } from '../middleware/index.js';
|
|
5
5
|
export class ApiController {
|
|
6
6
|
app;
|
|
7
7
|
service;
|
|
@@ -19,15 +19,15 @@ export class ApiController {
|
|
|
19
19
|
this.mapRoutes(app);
|
|
20
20
|
}
|
|
21
21
|
mapRoutes(app) {
|
|
22
|
-
app.get(`/api/${this.slug}`,
|
|
23
|
-
app.get(`/api/${this.slug}/all`,
|
|
24
|
-
app.get(`/api/${this.slug}/count`,
|
|
25
|
-
app.get(`/api/${this.slug}/:id`,
|
|
26
|
-
app.post(`/api/${this.slug}`,
|
|
27
|
-
app.patch(`/api/${this.slug}/batch`,
|
|
28
|
-
app.put(`/api/${this.slug}/:id`,
|
|
29
|
-
app.patch(`/api/${this.slug}/:id`,
|
|
30
|
-
app.delete(`/api/${this.slug}/:id`,
|
|
22
|
+
app.get(`/api/${this.slug}`, isAuthorized(), this.get.bind(this));
|
|
23
|
+
app.get(`/api/${this.slug}/all`, isAuthorized(), this.getAll.bind(this));
|
|
24
|
+
app.get(`/api/${this.slug}/count`, isAuthorized(), this.getCount.bind(this));
|
|
25
|
+
app.get(`/api/${this.slug}/:id`, isAuthorized(), this.getById.bind(this));
|
|
26
|
+
app.post(`/api/${this.slug}`, isAuthorized(), this.create.bind(this));
|
|
27
|
+
app.patch(`/api/${this.slug}/batch`, isAuthorized(), this.batchUpdate.bind(this));
|
|
28
|
+
app.put(`/api/${this.slug}/:id`, isAuthorized(), this.fullUpdateById.bind(this));
|
|
29
|
+
app.patch(`/api/${this.slug}/:id`, isAuthorized(), this.partialUpdateById.bind(this));
|
|
30
|
+
app.delete(`/api/${this.slug}/:id`, isAuthorized(), this.deleteById.bind(this));
|
|
31
31
|
}
|
|
32
32
|
validate(entity, isPartial = false) {
|
|
33
33
|
const validationErrors = this.service.validate(entity, isPartial);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { LoginResponseSpec, TokenResponseSpec, UserSpec, PublicUserSpec, passwordValidator, PublicUserContextSpec, } from '@loomcore/common/models';
|
|
2
2
|
import { entityUtils } from '@loomcore/common/utils';
|
|
3
3
|
import { BadRequestError, UnauthenticatedError } from '../errors/index.js';
|
|
4
|
-
import { isAuthenticated } from '../middleware/index.js';
|
|
5
4
|
import { apiUtils } from '../utils/index.js';
|
|
6
5
|
import { AuthService } from '../services/index.js';
|
|
6
|
+
import { isAuthorized } from '../middleware/index.js';
|
|
7
7
|
export class AuthController {
|
|
8
8
|
authService;
|
|
9
9
|
constructor(app, database) {
|
|
@@ -13,10 +13,10 @@ export class AuthController {
|
|
|
13
13
|
}
|
|
14
14
|
mapRoutes(app) {
|
|
15
15
|
app.post(`/api/auth/login`, this.login.bind(this), this.afterAuth.bind(this));
|
|
16
|
-
app.post(`/api/auth/register`,
|
|
16
|
+
app.post(`/api/auth/register`, isAuthorized(), this.registerUser.bind(this));
|
|
17
17
|
app.get(`/api/auth/refresh`, this.requestTokenUsingRefreshToken.bind(this));
|
|
18
|
-
app.get(`/api/auth/get-user-context`,
|
|
19
|
-
app.patch(`/api/auth/change-password`,
|
|
18
|
+
app.get(`/api/auth/get-user-context`, isAuthorized(), this.getUserContext.bind(this));
|
|
19
|
+
app.patch(`/api/auth/change-password`, isAuthorized(), this.changePassword.bind(this));
|
|
20
20
|
app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
|
|
21
21
|
app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
|
|
22
22
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApiController } from './api.controller.js';
|
|
2
|
-
import { isAuthenticated } from '../middleware/index.js';
|
|
3
2
|
import { apiUtils } from '../utils/index.js';
|
|
4
3
|
import { BadRequestError } from '../errors/index.js';
|
|
5
4
|
import { OrganizationService } from '../services/index.js';
|
|
5
|
+
import { isAuthorized } from '../middleware/index.js';
|
|
6
6
|
export class OrganizationsController extends ApiController {
|
|
7
7
|
orgService;
|
|
8
8
|
constructor(app, database) {
|
|
@@ -12,8 +12,8 @@ export class OrganizationsController extends ApiController {
|
|
|
12
12
|
}
|
|
13
13
|
mapRoutes(app) {
|
|
14
14
|
super.mapRoutes(app);
|
|
15
|
-
app.get(`/api/${this.slug}/get-by-name/:name`,
|
|
16
|
-
app.get(`/api/${this.slug}/get-by-code/:code`,
|
|
15
|
+
app.get(`/api/${this.slug}/get-by-name/:name`, isAuthorized(), this.getByName.bind(this));
|
|
16
|
+
app.get(`/api/${this.slug}/get-by-code/:code`, isAuthorized(), this.getByCode.bind(this));
|
|
17
17
|
}
|
|
18
18
|
async getByName(req, res, next) {
|
|
19
19
|
console.log('in OrganizationController.getByName');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UserSpec, PublicUserSpec } from '@loomcore/common/models';
|
|
2
2
|
import { ApiController } from './api.controller.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isAuthorized } from '../middleware/index.js';
|
|
4
4
|
import { UserService } from '../services/index.js';
|
|
5
5
|
export class UsersController extends ApiController {
|
|
6
6
|
userService;
|
|
@@ -10,13 +10,13 @@ export class UsersController extends ApiController {
|
|
|
10
10
|
this.userService = userService;
|
|
11
11
|
}
|
|
12
12
|
mapRoutes(app) {
|
|
13
|
-
app.get(`/api/${this.slug}`,
|
|
14
|
-
app.get(`/api/${this.slug}/all`,
|
|
15
|
-
app.get(`/api/${this.slug}/find`,
|
|
16
|
-
app.get(`/api/${this.slug}/count`,
|
|
17
|
-
app.get(`/api/${this.slug}/:id`,
|
|
18
|
-
app.post(`/api/${this.slug}`,
|
|
19
|
-
app.patch(`/api/${this.slug}/:id`,
|
|
20
|
-
app.delete(`/api/${this.slug}/:id`,
|
|
13
|
+
app.get(`/api/${this.slug}`, isAuthorized(), this.get.bind(this));
|
|
14
|
+
app.get(`/api/${this.slug}/all`, isAuthorized(), this.getAll.bind(this));
|
|
15
|
+
app.get(`/api/${this.slug}/find`, isAuthorized(), this.get.bind(this));
|
|
16
|
+
app.get(`/api/${this.slug}/count`, isAuthorized(), this.getCount.bind(this));
|
|
17
|
+
app.get(`/api/${this.slug}/:id`, isAuthorized(), this.getById.bind(this));
|
|
18
|
+
app.post(`/api/${this.slug}`, isAuthorized(), this.create.bind(this));
|
|
19
|
+
app.patch(`/api/${this.slug}/:id`, isAuthorized(), this.partialUpdateById.bind(this));
|
|
20
|
+
app.delete(`/api/${this.slug}/:id`, isAuthorized(), this.deleteById.bind(this));
|
|
21
21
|
}
|
|
22
22
|
}
|
package/dist/middleware/index.js
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { UserContextSpec } from '@loomcore/common/models';
|
|
2
|
+
import { UnauthenticatedError, UnauthorizedError } from '../errors/index.js';
|
|
3
|
+
import { JwtService } from '../services/index.js';
|
|
4
|
+
import { config } from '../config/index.js';
|
|
5
|
+
const isAuthorized = (allowedFeatures) => {
|
|
6
|
+
return (req, res, next) => {
|
|
7
|
+
let token = null;
|
|
8
|
+
if (req.headers?.authorization) {
|
|
9
|
+
let authHeader = req.headers.authorization;
|
|
10
|
+
const authHeaderArray = authHeader.split('Bearer ');
|
|
11
|
+
if (authHeaderArray?.length > 1) {
|
|
12
|
+
token = authHeaderArray[1];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (!token) {
|
|
16
|
+
throw new UnauthenticatedError();
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const rawPayload = JwtService.verify(token, config.clientSecret);
|
|
20
|
+
const userContext = UserContextSpec.decode(rawPayload);
|
|
21
|
+
req.userContext = userContext;
|
|
22
|
+
if (userContext.authorizations.some(authorization => authorization.feature === 'admin')) {
|
|
23
|
+
next();
|
|
24
|
+
}
|
|
25
|
+
else if (allowedFeatures && allowedFeatures.length) {
|
|
26
|
+
if (!userContext.authorizations.some(authorization => allowedFeatures?.includes(authorization.feature))) {
|
|
27
|
+
throw new UnauthorizedError();
|
|
28
|
+
}
|
|
29
|
+
next();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
next();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
throw new UnauthenticatedError();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export { isAuthorized };
|
package/package.json
CHANGED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { UserContextSpec } from '@loomcore/common/models';
|
|
2
|
-
import { UnauthenticatedError } from '../errors/index.js';
|
|
3
|
-
import { JwtService } from '../services/index.js';
|
|
4
|
-
import { config } from '../config/index.js';
|
|
5
|
-
export const isAuthenticated = (req, res, next) => {
|
|
6
|
-
let token = null;
|
|
7
|
-
if (req.headers?.authorization) {
|
|
8
|
-
let authHeader = req.headers.authorization;
|
|
9
|
-
const authHeaderArray = authHeader.split('Bearer ');
|
|
10
|
-
if (authHeaderArray?.length > 1) {
|
|
11
|
-
token = authHeaderArray[1];
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
if (token) {
|
|
15
|
-
try {
|
|
16
|
-
const rawPayload = JwtService.verify(token, config.clientSecret);
|
|
17
|
-
req.userContext = UserContextSpec.decode(rawPayload);
|
|
18
|
-
next();
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
throw new UnauthenticatedError();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
throw new UnauthenticatedError();
|
|
26
|
-
}
|
|
27
|
-
};
|