Skip to content

Commit 87318be

Browse files
committed
chore(api): add memberIdOrLfid support in param and user validation endpoints
- Add endpoints for detected identities, work history status, and user validation (identity/org) supporting both memberId and LFID - Implement memberIdOrLfid middleware for flexible ID resolution - Update permissions and roles for new endpoints and external service access - Add audit logging for user validation actions - Refactor and extend data-access-layer for new validation and activity count logic - Add migrations for memberUserValidations table
1 parent 1700357 commit 87318be

31 files changed

+547
-45
lines changed

backend/src/api/member/identity/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { safeWrap } from '@/middlewares/errorMiddleware'
2+
import { memberIdOrLfidMiddleware } from '@/middlewares/memberIdOrLfidMiddleware'
23

34
export default (app) => {
45
// Member Identity List
@@ -15,4 +16,8 @@ export default (app) => {
1516

1617
// Member Identity Delete
1718
app.delete(`/member/:memberId/identity/:id`, safeWrap(require('./memberIdentityDelete').default))
19+
20+
app.get(`/member/:memberIdOrLfid/detected-identity`, memberIdOrLfidMiddleware, safeWrap(require('./memberIdentityDetectedList').default))
21+
22+
app.post(`/member/:memberIdOrLfid/user-validation`, memberIdOrLfidMiddleware, safeWrap(require('./memberIdentityUserValidation').default))
1823
}

backend/src/api/member/identity/memberIdentityCreate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1717
* @response 429 - Too many requests
1818
*/
1919
export default async (req, res) => {
20-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
20+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityCreate)
2121

2222
const memberIdentityService = new MemberIdentityService(req)
2323

backend/src/api/member/identity/memberIdentityCreateMultiple.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1717
* @response 429 - Too many requests
1818
*/
1919
export default async (req, res) => {
20-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
20+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityCreate)
2121

2222
const memberIdentityService = new MemberIdentityService(req)
2323

backend/src/api/member/identity/memberIdentityDelete.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1818
* @response 429 - Too many requests
1919
*/
2020
export default async (req, res) => {
21-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
21+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityDestroy)
2222

2323
const memberIdentityService = new MemberIdentityService(req)
2424

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import MemberIdentityService from '@/services/member/memberIdentityService'
2+
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
6+
/**
7+
* GET /member/:memberIdOrLfid/detected-identity
8+
* @summary Query detected identities for a given LFID or memberID
9+
* @tag Members
10+
* @security Bearer
11+
* @description Query detected identities for a given LFID or memberID
12+
* @pathParam {string} memberIdOrLfid - member ID or LFID
13+
* @response 200 - Ok
14+
* @responseContent {MemberList} 200.application/json
15+
* @responseExample {MemberList} 200.application/json.Member
16+
* @response 401 - Unauthorized
17+
* @response 404 - Not found
18+
* @response 429 - Too many requests
19+
*/
20+
export default async (req, res) => {
21+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityRead)
22+
23+
const memberIdentityService = new MemberIdentityService(req)
24+
25+
const payload = await memberIdentityService.detectedList(req.memberId)
26+
27+
await req.responseHandler.success(req, res, payload)
28+
}

backend/src/api/member/identity/memberIdentityList.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1818
* @response 429 - Too many requests
1919
*/
2020
export default async (req, res) => {
21-
new PermissionChecker(req).validateHas(Permissions.values.memberRead)
21+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityRead)
2222

2323
const memberIdentityService = new MemberIdentityService(req)
2424

backend/src/api/member/identity/memberIdentityUpdate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1818
* @response 429 - Too many requests
1919
*/
2020
export default async (req, res) => {
21-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
21+
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityEdit)
2222

2323
const memberIdentityService = new MemberIdentityService(req)
2424

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import MemberIdentityService from '@/services/member/memberIdentityService'
2+
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
6+
/**
7+
* POST /member/:memberIdOrLfid/user-validation
8+
* @summary Create user validation for member identity
9+
* @tag Members
10+
* @security Bearer
11+
* @description Creates user validation when identity is accepted or rejected.
12+
* @pathParam {string} memberIdOrLfid - member ID or LFID
13+
* @response 200 - Ok
14+
* @responseContent {MemberList} 200.application/json
15+
* @responseExample {MemberList} 200.application/json.MemberIdentity
16+
* @response 401 - Unauthorized
17+
* @response 429 - Too many requests
18+
*/
19+
export default async (req, res) => {
20+
new PermissionChecker(req).validateHas(Permissions.values.memberUserValidationCreate)
21+
22+
const memberIdentityService = new MemberIdentityService(req)
23+
24+
const payload = await memberIdentityService.userValidation(req.memberId, req.body)
25+
26+
await req.responseHandler.success(req, res, payload)
27+
}

backend/src/api/member/organization/index.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { safeWrap } from '@/middlewares/errorMiddleware'
2+
import { memberIdOrLfidMiddleware } from '@/middlewares/memberIdOrLfidMiddleware'
23

34
export default (app) => {
45
// Member Organiaztion List
5-
app.get(`/member/:memberId/organization`, safeWrap(require('./memberOrganizationList').default))
6+
app.get(
7+
`/member/:memberIdOrLfid/organization`,
8+
memberIdOrLfidMiddleware,
9+
safeWrap(require('./memberOrganizationList').default),
10+
)
611

712
// Member Organiaztion Create
813
app.post(
@@ -21,4 +26,16 @@ export default (app) => {
2126
`/member/:memberId/organization/:id`,
2227
safeWrap(require('./memberOrganizationDelete').default),
2328
)
29+
30+
app.get(
31+
`/member/:memberIdOrLfid/organization/status`,
32+
memberIdOrLfidMiddleware,
33+
safeWrap(require('./memberOrganizationStatus').default),
34+
)
35+
36+
app.post(
37+
`/member/:memberIdOrLfid/organization/user-validation`,
38+
memberIdOrLfidMiddleware,
39+
safeWrap(require('./memberOrganizationUserValidation').default),
40+
)
2441
}

backend/src/api/member/organization/memberOrganizationCreate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1616
* @response 429 - Too many requests
1717
*/
1818
export default async (req, res) => {
19-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
19+
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationCreate)
2020

2121
const memberOrganizationsService = new MemberOrganizationsService(req)
2222

backend/src/api/member/organization/memberOrganizationDelete.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1818
* @response 429 - Too many requests
1919
*/
2020
export default async (req, res) => {
21-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
21+
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationDestroy)
2222

2323
const memberOrganizationsService = new MemberOrganizationsService(req)
2424

backend/src/api/member/organization/memberOrganizationList.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Permissions from '../../../security/permissions'
44
import PermissionChecker from '../../../services/user/permissionChecker'
55

66
/**
7-
* GET /member/:memberId/organization
7+
* GET /member/:memberIdOrLfid/organization
88
* @summary Query member organizations
99
* @tag Members
1010
* @security Bearer
@@ -16,11 +16,11 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1616
* @response 429 - Too many requests
1717
*/
1818
export default async (req, res) => {
19-
new PermissionChecker(req).validateHas(Permissions.values.memberRead)
19+
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationRead)
2020

2121
const memberOrganizationsService = new MemberOrganizationsService(req)
2222

23-
const payload = await memberOrganizationsService.list(req.params.memberId)
23+
const payload = await memberOrganizationsService.list(req.memberId)
2424

2525
await req.responseHandler.success(req, res, payload)
2626
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import MemberOrganizationsService from '@/services/member/memberOrganizationsService'
2+
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
6+
/**
7+
* GET /member/:memberIdOrLfid/organization/status
8+
* @summary Check if work history records exist
9+
* @tag Members
10+
* @security Bearer
11+
* @description Returns { status: true } if work history exists, otherwise { status: false }
12+
* @response 200 - Ok
13+
* @responseContent {MemberList} 200.application/json
14+
* @responseExample {MemberList} 200.application/json.Organization
15+
* @response 401 - Unauthorized
16+
* @response 429 - Too many requests
17+
*/
18+
export default async (req, res) => {
19+
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationRead)
20+
21+
const memberOrganizationsService = new MemberOrganizationsService(req)
22+
23+
const status = await memberOrganizationsService.getStatus(req.memberId)
24+
25+
await req.responseHandler.success(req, res, { status })
26+
}

backend/src/api/member/organization/memberOrganizationUpdate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
1818
* @response 429 - Too many requests
1919
*/
2020
export default async (req, res) => {
21-
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
21+
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationEdit)
2222

2323
const memberOrganizationsService = new MemberOrganizationsService(req)
2424

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import MemberOrganizationsService from '@/services/member/memberOrganizationsService'
2+
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
6+
/**
7+
* POST /member/:memberIdOrLfid/organization/user-validation
8+
* @summary Create user validation for member organization
9+
* @tag Members
10+
* @security Bearer
11+
* @description Creates user validation when organization is created, updated or deleted.
12+
* @pathParam {string} memberIdOrLfid - member ID or LFID
13+
* @response 200 - Ok
14+
* @responseContent {MemberList} 200.application/json
15+
* @responseExample {MemberList} 200.application/json.Organization
16+
* @response 401 - Unauthorized
17+
* @response 429 - Too many requests
18+
*/
19+
export default async (req, res) => {
20+
new PermissionChecker(req).validateHas(Permissions.values.memberUserValidationCreate)
21+
22+
const memberOrganizationsService = new MemberOrganizationsService(req)
23+
24+
const payload = await memberOrganizationsService.userValidation(req.memberId, req.body)
25+
26+
await req.responseHandler.success(req, res, payload)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
drop table if exists "memberUserValidations";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
create table "memberUserValidations"(
2+
id uuid primary key default uuid_generate_v4(),
3+
"memberId" uuid not null references members(id),
4+
action varchar(255) not null,
5+
type varchar(255) not null,
6+
details jsonb not null
7+
-- todo: ask joana if we need timestamp or can reuse createdAt/updatedAt
8+
-- createdAt timestamp with time zone default now(),
9+
-- updatedAt timestamp with time zone default now(),
10+
-- deletedAt timestamp with time zone
11+
);
12+
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { validateUUID } from '@crowd/common'
2+
import { lfidToMemberId } from '@crowd/data-access-layer'
3+
import SequelizeRepository from '@/database/repositories/sequelizeRepository'
4+
5+
/**
6+
* Middleware to resolve `memberIdOrLfid` route param to a member UUID.
7+
*
8+
* - If a memberId (uuid), sets `req.memberId` and continues.
9+
* - If an lfid (used by external services), resolves to memberId via db lookup.
10+
* Used in routes that accept either type for member identification.
11+
*/
12+
export async function memberIdOrLfidMiddleware(req, res, next) {
13+
const identifier = req.params.memberIdOrLfid
14+
const qx = SequelizeRepository.getQueryExecutor({ database: req.database, ...req })
15+
16+
if (validateUUID(identifier)) {
17+
req.memberId = identifier
18+
return next()
19+
}
20+
21+
const memberId = await lfidToMemberId(qx, identifier)
22+
if (!memberId) {
23+
return res.status(404).json({ error: 'Member not found for given lfid!' })
24+
}
25+
26+
req.memberId = memberId
27+
return next()
28+
}

backend/src/security/permissions.ts

+36
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,42 @@ class Permissions {
8484
id: 'memberAutocomplete',
8585
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly],
8686
},
87+
memberIdentityCreate: {
88+
id: 'memberIdentityCreate',
89+
allowedRoles: [roles.admin, roles.projectAdmin],
90+
},
91+
memberIdentityEdit: {
92+
id: 'memberIdentityEdit',
93+
allowedRoles: [roles.admin, roles.projectAdmin],
94+
},
95+
memberIdentityDestroy: {
96+
id: 'memberIdentityDestroy',
97+
allowedRoles: [roles.admin, roles.projectAdmin],
98+
},
99+
memberIdentityRead: {
100+
id: 'memberIdentityRead',
101+
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly, roles.externalService],
102+
},
103+
memberOrganizationCreate: {
104+
id: 'memberOrganizationCreate',
105+
allowedRoles: [roles.admin, roles.projectAdmin],
106+
},
107+
memberOrganizationEdit: {
108+
id: 'memberOrganizationEdit',
109+
allowedRoles: [roles.admin, roles.projectAdmin],
110+
},
111+
memberOrganizationDestroy: {
112+
id: 'memberOrganizationDestroy',
113+
allowedRoles: [roles.admin, roles.projectAdmin],
114+
},
115+
memberOrganizationRead: {
116+
id: 'memberOrganizationRead',
117+
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly, roles.externalService],
118+
},
119+
memberUserValidationCreate: {
120+
id: 'memberUserValidationCreate',
121+
allowedRoles: [roles.externalService],
122+
},
87123
activityImport: {
88124
id: 'activityImport',
89125
allowedRoles: [roles.admin, roles.projectAdmin],

backend/src/security/roles.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class Roles {
44
admin: 'admin',
55
readonly: 'readonly',
66
projectAdmin: 'projectAdmin',
7+
externalService: 'externalService',
78
}
89
}
910
}

0 commit comments

Comments
 (0)