@nacre.work/api 0.23.10 → 0.25.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/adapters.d.ts +47 -1
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +141 -5
- package/dist/adapters.js.map +1 -1
- package/dist/login.d.ts +9 -2
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +5 -1
- package/dist/login.js.map +1 -1
- package/dist/pagination.d.ts +16 -1
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +15 -18
- package/dist/pagination.js.map +1 -1
- package/dist/second-factor.d.ts +16 -2
- package/dist/second-factor.d.ts.map +1 -1
- package/dist/second-factor.js +30 -5
- package/dist/second-factor.js.map +1 -1
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +157 -42
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import { MIN_PASSWORD_LENGTH } from './recovery.js';
|
|
|
13
13
|
import { looksLikeEmail } from './principals.js';
|
|
14
14
|
import { clientSource } from './source.js';
|
|
15
15
|
import { auditFormat, auditJson, readAuditQuery, toCsv, toNdjson, } from './audit-export.js';
|
|
16
|
-
import { readPage } from './pagination.js';
|
|
16
|
+
import { decodeCursor, readPage } from './pagination.js';
|
|
17
17
|
/**
|
|
18
18
|
* The default body cap, in bytes.
|
|
19
19
|
*
|
|
@@ -868,6 +868,37 @@ function sendSessionOutcome(res, outcome, instance, requestId) {
|
|
|
868
868
|
});
|
|
869
869
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
870
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* Match a route whose captured segments are percent-decoded, or `null`.
|
|
873
|
+
*
|
|
874
|
+
* WHATWG `URL` leaves an invalid escape in `pathname` verbatim, so a bare
|
|
875
|
+
* `decodeURIComponent` over a captured segment throws `URIError` on
|
|
876
|
+
* `/v1/documents/%ZZ` — which the error boundary turned into a `500` and an
|
|
877
|
+
* audit row saying `error`, where the right answer is the `404` every other
|
|
878
|
+
* unknown id gets. A malformed escape names nothing, so here it is a path
|
|
879
|
+
* that does not match: the request falls through to the same `notFound` an
|
|
880
|
+
* unrouted path gets, and any authenticated caller stops being able to fill
|
|
881
|
+
* the journal with `error` rows on demand. Twelve routes decode a segment;
|
|
882
|
+
* this is the one place they all do it, and eslint refuses a bare
|
|
883
|
+
* `decodeURIComponent` in this package so the thirteenth cannot be written
|
|
884
|
+
* the old way.
|
|
885
|
+
*/
|
|
886
|
+
function pathMatch(pattern, instance) {
|
|
887
|
+
const matched = pattern.exec(instance);
|
|
888
|
+
if (matched === null)
|
|
889
|
+
return null;
|
|
890
|
+
try {
|
|
891
|
+
for (let i = 1; i < matched.length; i += 1) {
|
|
892
|
+
const raw = matched[i];
|
|
893
|
+
if (raw !== undefined)
|
|
894
|
+
matched[i] = decodeURIComponent(raw);
|
|
895
|
+
}
|
|
896
|
+
return matched;
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
871
902
|
async function handleAuth(req, res, instance, requestId, options) {
|
|
872
903
|
// Every route here produces a credential and therefore takes a body, with
|
|
873
904
|
// exactly one exception: `/v1/auth/methods` is a **read**, and the contract
|
|
@@ -1064,10 +1095,15 @@ async function handleAuth(req, res, instance, requestId, options) {
|
|
|
1064
1095
|
return;
|
|
1065
1096
|
}
|
|
1066
1097
|
}
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1098
|
+
// Deliberately not awaited. The body is the same `204` either way, but the
|
|
1099
|
+
// *time* to it was not: a miss returns after one SELECT and a hit does a
|
|
1100
|
+
// full SMTP round trip — hundreds of milliseconds against a couple — so an
|
|
1101
|
+
// awaited request() made the one endpoint reachable without a credential a
|
|
1102
|
+
// timing oracle for "does this address have an account", which is exactly
|
|
1103
|
+
// what the module's own header says it must not be. The failure is still
|
|
1104
|
+
// swallowed into a log line: an unhandled rejection would take the process
|
|
1105
|
+
// down over a relay being briefly unreachable.
|
|
1106
|
+
void options.recovery.request(email.email).catch((error) => {
|
|
1071
1107
|
logger.warn('could not start a password recovery', {
|
|
1072
1108
|
request_id: requestId,
|
|
1073
1109
|
error: String(error).slice(0, 200),
|
|
@@ -2348,9 +2384,9 @@ async function handle(req, res, options) {
|
|
|
2348
2384
|
send(res, outcome.unchanged ? 200 : 202, { document_id: outcome.documentId, job_id: outcome.jobId, status: outcome.unchanged ? 'indexed' : 'queued' }, requestId);
|
|
2349
2385
|
return;
|
|
2350
2386
|
}
|
|
2351
|
-
const documentMatch = /^\/v1\/documents\/([^/]+)
|
|
2387
|
+
const documentMatch = pathMatch(/^\/v1\/documents\/([^/]+)$/, instance);
|
|
2352
2388
|
if (req.method === 'DELETE' && documentMatch) {
|
|
2353
|
-
const id =
|
|
2389
|
+
const id = documentMatch[1];
|
|
2354
2390
|
const removed = await options.ingest.remove(auth, id);
|
|
2355
2391
|
await options.audit.write({
|
|
2356
2392
|
orgId: auth.orgId,
|
|
@@ -2369,8 +2405,51 @@ async function handle(req, res, options) {
|
|
|
2369
2405
|
send(res, 204, null, requestId);
|
|
2370
2406
|
return;
|
|
2371
2407
|
}
|
|
2408
|
+
const retryMatch = pathMatch(/^\/v1\/documents\/([^/]+)\/retry$/, instance);
|
|
2409
|
+
if (req.method === 'POST' && retryMatch) {
|
|
2410
|
+
const id = retryMatch[1];
|
|
2411
|
+
if (options.documents.retry === undefined) {
|
|
2412
|
+
const problem = notFound(instance, requestId);
|
|
2413
|
+
send(res, problem.status, problem.toJSON(), requestId);
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
const outcome = await options.documents.retry(auth, id);
|
|
2417
|
+
await options.audit.write({
|
|
2418
|
+
orgId: auth.orgId,
|
|
2419
|
+
actor: `${auth.principal.type}:${auth.principal.id}`,
|
|
2420
|
+
action: 'retry_document',
|
|
2421
|
+
result: outcome === 'requeued' ? 'allow' : 'deny',
|
|
2422
|
+
target: { document_id: id },
|
|
2423
|
+
detail: { document_id: id, outcome },
|
|
2424
|
+
requestId,
|
|
2425
|
+
});
|
|
2426
|
+
if (outcome === 'unreachable') {
|
|
2427
|
+
const problem = notFound(instance, requestId);
|
|
2428
|
+
send(res, problem.status, problem.toJSON(), requestId);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
if (outcome === 'not-failed') {
|
|
2432
|
+
const problem = new Problem({
|
|
2433
|
+
type: 'https://nacre.work/errors/conflict',
|
|
2434
|
+
title: 'Conflict',
|
|
2435
|
+
status: 409,
|
|
2436
|
+
detail: 'This document has not failed, so there is nothing to retry. ' +
|
|
2437
|
+
'Only a document whose status is `failed` can be requeued this way; ' +
|
|
2438
|
+
'send it again through POST /v1/documents to re-index one that has not.',
|
|
2439
|
+
instance,
|
|
2440
|
+
requestId,
|
|
2441
|
+
});
|
|
2442
|
+
send(res, problem.status, problem.toJSON(), requestId);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
// `204`, and never the document, for the same reason the PATCH beside it
|
|
2446
|
+
// does: rule 6 means a caller may hold `write` without `read`, so a body
|
|
2447
|
+
// describing what was requeued would be a read this caller has not got.
|
|
2448
|
+
send(res, 204, null, requestId);
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2372
2451
|
if (req.method === 'PATCH' && documentMatch) {
|
|
2373
|
-
const id =
|
|
2452
|
+
const id = documentMatch[1];
|
|
2374
2453
|
if (options.documents.updateMetadata === undefined) {
|
|
2375
2454
|
const problem = notFound(instance, requestId);
|
|
2376
2455
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
@@ -2417,7 +2496,7 @@ async function handle(req, res, options) {
|
|
|
2417
2496
|
return;
|
|
2418
2497
|
}
|
|
2419
2498
|
if (req.method === 'GET' && documentMatch) {
|
|
2420
|
-
const id =
|
|
2499
|
+
const id = documentMatch[1];
|
|
2421
2500
|
const document = await options.documents.read(auth, id);
|
|
2422
2501
|
if (document === undefined) {
|
|
2423
2502
|
// T8. The same response for "no such document" and "another
|
|
@@ -2448,9 +2527,9 @@ async function handle(req, res, options) {
|
|
|
2448
2527
|
send(res, 200, document, requestId);
|
|
2449
2528
|
return;
|
|
2450
2529
|
}
|
|
2451
|
-
const jobMatch = /^\/v1\/jobs\/([^/]+)
|
|
2530
|
+
const jobMatch = pathMatch(/^\/v1\/jobs\/([^/]+)$/, instance);
|
|
2452
2531
|
if (req.method === 'GET' && jobMatch && options.jobs !== undefined) {
|
|
2453
|
-
const id =
|
|
2532
|
+
const id = jobMatch[1];
|
|
2454
2533
|
const job = await options.jobs.read(auth, id);
|
|
2455
2534
|
if (job === undefined) {
|
|
2456
2535
|
// A job names a document, so it is as much of an oracle as the document
|
|
@@ -2816,7 +2895,7 @@ async function handle(req, res, options) {
|
|
|
2816
2895
|
}
|
|
2817
2896
|
if (instance === '/v1/workspaces' && options.workspaces !== undefined) {
|
|
2818
2897
|
if (req.method === 'GET') {
|
|
2819
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
2898
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
2820
2899
|
if (page instanceof Problem) {
|
|
2821
2900
|
send(res, page.status, page.toJSON(), requestId);
|
|
2822
2901
|
return;
|
|
@@ -2889,7 +2968,7 @@ async function handle(req, res, options) {
|
|
|
2889
2968
|
}
|
|
2890
2969
|
}
|
|
2891
2970
|
if (req.method === 'GET' && instance === '/v1/layers' && options.layers !== undefined) {
|
|
2892
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
2971
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
2893
2972
|
if (page instanceof Problem) {
|
|
2894
2973
|
send(res, page.status, page.toJSON(), requestId);
|
|
2895
2974
|
return;
|
|
@@ -2979,7 +3058,7 @@ async function handle(req, res, options) {
|
|
|
2979
3058
|
return;
|
|
2980
3059
|
}
|
|
2981
3060
|
if (req.method === 'GET' && instance === '/v1/grants' && options.grants !== undefined) {
|
|
2982
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
3061
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
2983
3062
|
if (page instanceof Problem) {
|
|
2984
3063
|
send(res, page.status, page.toJSON(), requestId);
|
|
2985
3064
|
return;
|
|
@@ -3018,9 +3097,9 @@ async function handle(req, res, options) {
|
|
|
3018
3097
|
send(res, 201, grantJson(issued), requestId);
|
|
3019
3098
|
return;
|
|
3020
3099
|
}
|
|
3021
|
-
const grantMatch = /^\/v1\/grants\/([^/]+)
|
|
3100
|
+
const grantMatch = pathMatch(/^\/v1\/grants\/([^/]+)$/, instance);
|
|
3022
3101
|
if (req.method === 'DELETE' && grantMatch && options.grants !== undefined) {
|
|
3023
|
-
const id =
|
|
3102
|
+
const id = grantMatch[1];
|
|
3024
3103
|
const revoked = await options.grants.revoke(auth, id);
|
|
3025
3104
|
// Written before the response either way. A revocation nobody can prove
|
|
3026
3105
|
// happened is not a revocation an auditor will accept, and a *refused*
|
|
@@ -3339,7 +3418,7 @@ async function handle(req, res, options) {
|
|
|
3339
3418
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
3340
3419
|
return;
|
|
3341
3420
|
}
|
|
3342
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
3421
|
+
const page = readPage(url.searchParams, instance, requestId, 'sequence');
|
|
3343
3422
|
if (page instanceof Problem) {
|
|
3344
3423
|
send(res, page.status, page.toJSON(), requestId);
|
|
3345
3424
|
return;
|
|
@@ -3609,11 +3688,31 @@ async function handle(req, res, options) {
|
|
|
3609
3688
|
// consent theirs to give. `404` for anything else, because an agent they
|
|
3610
3689
|
// cannot see and one that does not exist are the same answer here as
|
|
3611
3690
|
// everywhere.
|
|
3691
|
+
//
|
|
3692
|
+
// The whole listing, not one page of it. A `find` over the first 200
|
|
3693
|
+
// used to stand here, so in an organization with more accounts than
|
|
3694
|
+
// that, approving one ordered past the cut answered `404`
|
|
3695
|
+
// indistinguishably from "no such agent" — a valid consent refused with
|
|
3696
|
+
// nothing on the screen to say why. The loop terminates because the
|
|
3697
|
+
// listing seeks on `(created_at, id)`, and it stops early on a hit.
|
|
3612
3698
|
if (!delegating) {
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3699
|
+
let visible = false;
|
|
3700
|
+
if (options.serviceAccounts !== undefined) {
|
|
3701
|
+
let after;
|
|
3702
|
+
for (;;) {
|
|
3703
|
+
const found = await options.serviceAccounts.list(auth, { limit: 200, after });
|
|
3704
|
+
if (found.items.some((a) => a.id === serviceAccountId)) {
|
|
3705
|
+
visible = true;
|
|
3706
|
+
break;
|
|
3707
|
+
}
|
|
3708
|
+
if (found.nextCursor === null)
|
|
3709
|
+
break;
|
|
3710
|
+
after = decodeCursor(found.nextCursor, 'uuid');
|
|
3711
|
+
if (after === undefined)
|
|
3712
|
+
break;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
if (!visible) {
|
|
3617
3716
|
const problem = notFound(instance, requestId);
|
|
3618
3717
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
3619
3718
|
return;
|
|
@@ -3628,10 +3727,26 @@ async function handle(req, res, options) {
|
|
|
3628
3727
|
// restriction they wrote is not the one they meant. `404` for an unknown
|
|
3629
3728
|
// id and for an unreadable one alike, on invariant I6.
|
|
3630
3729
|
if (delegating && narrowing !== undefined && narrowing.length > 0) {
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3730
|
+
// The whole listing, for the reason the agent lookup above walks its
|
|
3731
|
+
// pages: one fixed page refused any layer ordered past the cut. The
|
|
3732
|
+
// loop stops early once every named layer has been seen.
|
|
3733
|
+
const readable = new Set();
|
|
3734
|
+
if (options.layers !== undefined) {
|
|
3735
|
+
let after;
|
|
3736
|
+
for (;;) {
|
|
3737
|
+
const found = await options.layers.list(auth, { limit: 500, after });
|
|
3738
|
+
for (const layer of found.items)
|
|
3739
|
+
readable.add(layer.id);
|
|
3740
|
+
if (narrowing.every((l) => readable.has(l.id)))
|
|
3741
|
+
break;
|
|
3742
|
+
if (found.nextCursor === null)
|
|
3743
|
+
break;
|
|
3744
|
+
after = decodeCursor(found.nextCursor, 'uuid');
|
|
3745
|
+
if (after === undefined)
|
|
3746
|
+
break;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
if (narrowing.some((l) => !readable.has(l.id))) {
|
|
3635
3750
|
const problem = notFound(instance, requestId);
|
|
3636
3751
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
3637
3752
|
return;
|
|
@@ -3723,7 +3838,7 @@ async function handle(req, res, options) {
|
|
|
3723
3838
|
return;
|
|
3724
3839
|
}
|
|
3725
3840
|
if (req.method === 'GET') {
|
|
3726
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
3841
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
3727
3842
|
if (page instanceof Problem) {
|
|
3728
3843
|
send(res, page.status, page.toJSON(), requestId);
|
|
3729
3844
|
return;
|
|
@@ -3782,14 +3897,14 @@ async function handle(req, res, options) {
|
|
|
3782
3897
|
return;
|
|
3783
3898
|
}
|
|
3784
3899
|
}
|
|
3785
|
-
const accountMatch = /^\/v1\/service-accounts\/([^/]+)
|
|
3900
|
+
const accountMatch = pathMatch(/^\/v1\/service-accounts\/([^/]+)$/, instance);
|
|
3786
3901
|
if (req.method === 'DELETE' && accountMatch && options.serviceAccounts !== undefined) {
|
|
3787
3902
|
if (!administers(auth)) {
|
|
3788
3903
|
const problem = notFound(instance, requestId);
|
|
3789
3904
|
send(res, problem.status, problem.toJSON(), requestId);
|
|
3790
3905
|
return;
|
|
3791
3906
|
}
|
|
3792
|
-
const id =
|
|
3907
|
+
const id = accountMatch[1];
|
|
3793
3908
|
const revoked = await options.serviceAccounts.revoke(auth, id);
|
|
3794
3909
|
await options.audit.write({
|
|
3795
3910
|
orgId: auth.orgId,
|
|
@@ -3838,7 +3953,7 @@ async function handle(req, res, options) {
|
|
|
3838
3953
|
}
|
|
3839
3954
|
if (instance === '/v1/users' && options.users !== undefined) {
|
|
3840
3955
|
if (req.method === 'GET') {
|
|
3841
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
3956
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
3842
3957
|
if (page instanceof Problem) {
|
|
3843
3958
|
send(res, page.status, page.toJSON(), requestId);
|
|
3844
3959
|
return;
|
|
@@ -3924,9 +4039,9 @@ async function handle(req, res, options) {
|
|
|
3924
4039
|
return;
|
|
3925
4040
|
}
|
|
3926
4041
|
}
|
|
3927
|
-
const userMatch = /^\/v1\/users\/([^/]+)
|
|
4042
|
+
const userMatch = pathMatch(/^\/v1\/users\/([^/]+)$/, instance);
|
|
3928
4043
|
if (userMatch && options.users !== undefined) {
|
|
3929
|
-
const id =
|
|
4044
|
+
const id = userMatch[1];
|
|
3930
4045
|
if (req.method === 'DELETE') {
|
|
3931
4046
|
// Disabled, never deleted, which is what `DELETE` means on every
|
|
3932
4047
|
// removable thing here: a document is tombstoned, a key is revoked, and
|
|
@@ -4047,9 +4162,9 @@ async function handle(req, res, options) {
|
|
|
4047
4162
|
return;
|
|
4048
4163
|
}
|
|
4049
4164
|
}
|
|
4050
|
-
const passwordMatch = /^\/v1\/users\/([^/]+)\/password
|
|
4165
|
+
const passwordMatch = pathMatch(/^\/v1\/users\/([^/]+)\/password$/, instance);
|
|
4051
4166
|
if (req.method === 'POST' && passwordMatch && options.users !== undefined) {
|
|
4052
|
-
const id =
|
|
4167
|
+
const id = passwordMatch[1];
|
|
4053
4168
|
const reset = await options.users.resetPassword(auth, id);
|
|
4054
4169
|
const refused = typeof reset === 'string' ? reset : undefined;
|
|
4055
4170
|
await options.audit.write({
|
|
@@ -4085,7 +4200,7 @@ async function handle(req, res, options) {
|
|
|
4085
4200
|
}
|
|
4086
4201
|
if (instance === '/v1/groups' && options.groups !== undefined) {
|
|
4087
4202
|
if (req.method === 'GET') {
|
|
4088
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
4203
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
4089
4204
|
if (page instanceof Problem) {
|
|
4090
4205
|
send(res, page.status, page.toJSON(), requestId);
|
|
4091
4206
|
return;
|
|
@@ -4136,9 +4251,9 @@ async function handle(req, res, options) {
|
|
|
4136
4251
|
return;
|
|
4137
4252
|
}
|
|
4138
4253
|
}
|
|
4139
|
-
const groupMatch = /^\/v1\/groups\/([^/]+)
|
|
4254
|
+
const groupMatch = pathMatch(/^\/v1\/groups\/([^/]+)$/, instance);
|
|
4140
4255
|
if (req.method === 'DELETE' && groupMatch && options.groups !== undefined) {
|
|
4141
|
-
const id =
|
|
4256
|
+
const id = groupMatch[1];
|
|
4142
4257
|
const removed = await options.groups.remove(auth, id);
|
|
4143
4258
|
await options.audit.write({
|
|
4144
4259
|
orgId: auth.orgId,
|
|
@@ -4157,11 +4272,11 @@ async function handle(req, res, options) {
|
|
|
4157
4272
|
send(res, 204, null, requestId);
|
|
4158
4273
|
return;
|
|
4159
4274
|
}
|
|
4160
|
-
const membersMatch = /^\/v1\/groups\/([^/]+)\/members
|
|
4275
|
+
const membersMatch = pathMatch(/^\/v1\/groups\/([^/]+)\/members$/, instance);
|
|
4161
4276
|
if (membersMatch && options.groups !== undefined) {
|
|
4162
|
-
const groupId =
|
|
4277
|
+
const groupId = membersMatch[1];
|
|
4163
4278
|
if (req.method === 'GET') {
|
|
4164
|
-
const page = readPage(url.searchParams, instance, requestId);
|
|
4279
|
+
const page = readPage(url.searchParams, instance, requestId, 'uuid');
|
|
4165
4280
|
if (page instanceof Problem) {
|
|
4166
4281
|
send(res, page.status, page.toJSON(), requestId);
|
|
4167
4282
|
return;
|
|
@@ -4213,11 +4328,11 @@ async function handle(req, res, options) {
|
|
|
4213
4328
|
// `{type}/{id}` rather than `{id}` alone: the edge is keyed by which member
|
|
4214
4329
|
// column it uses, so a bare uuid does not identify one. Same shape `grants`
|
|
4215
4330
|
// uses for the other end of the same relationship.
|
|
4216
|
-
const memberMatch = /^\/v1\/groups\/([^/]+)\/members\/(user|group)\/([^/]+)
|
|
4331
|
+
const memberMatch = pathMatch(/^\/v1\/groups\/([^/]+)\/members\/(user|group)\/([^/]+)$/, instance);
|
|
4217
4332
|
if (req.method === 'DELETE' && memberMatch && options.groups !== undefined) {
|
|
4218
|
-
const groupId =
|
|
4333
|
+
const groupId = memberMatch[1];
|
|
4219
4334
|
const type = memberMatch[2];
|
|
4220
|
-
const memberId =
|
|
4335
|
+
const memberId = memberMatch[3];
|
|
4221
4336
|
const removed = await options.groups.removeMember(auth, groupId, { type, id: memberId });
|
|
4222
4337
|
await options.audit.write({
|
|
4223
4338
|
orgId: auth.orgId,
|