scimgateway 5.0.9 → 5.0.11
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 +13 -0
- package/lib/helper-rest.ts +13 -9
- package/lib/scimgateway.ts +38 -12
- package/lib/utils-scim.ts +28 -28
- package/lib/utils.ts +11 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1111,6 +1111,19 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1111
1111
|
|
|
1112
1112
|
## Change log
|
|
1113
1113
|
|
|
1114
|
+
### v5.0.11
|
|
1115
|
+
|
|
1116
|
+
[Fixed]
|
|
1117
|
+
|
|
1118
|
+
- OAuth token response on error missing error_description in v5
|
|
1119
|
+
- HelperRest doRequest() now also includes retry logic on invalid token that has not expired - will renew token
|
|
1120
|
+
|
|
1121
|
+
### v5.0.10
|
|
1122
|
+
|
|
1123
|
+
[Improved]
|
|
1124
|
+
|
|
1125
|
+
- OAuth token request now accept missing or invalid Content-Type header
|
|
1126
|
+
|
|
1114
1127
|
### v5.0.9
|
|
1115
1128
|
|
|
1116
1129
|
[Improved]
|
package/lib/helper-rest.ts
CHANGED
|
@@ -166,13 +166,11 @@ export class HelperRest {
|
|
|
166
166
|
const response = await this.doRequest(baseEntity, method, tokenUrl, form, ctx, connOpt)
|
|
167
167
|
if (!response.body) {
|
|
168
168
|
const err = new Error(`[${action}] No data retrieved from: ${method} ${tokenUrl}`)
|
|
169
|
-
this.lock.release()
|
|
170
169
|
throw (err)
|
|
171
170
|
}
|
|
172
171
|
const jbody = response.body
|
|
173
172
|
if (jbody.error) {
|
|
174
173
|
const err = new Error(`[${action}] Error message: ${jbody.error_description}`)
|
|
175
|
-
this.lock.release()
|
|
176
174
|
throw (err)
|
|
177
175
|
}
|
|
178
176
|
if (this.config_entity[baseEntity]?.connection?.auth?.type === 'token') { // in case response using token instead of access_token
|
|
@@ -180,7 +178,6 @@ export class HelperRest {
|
|
|
180
178
|
else if (jbody.accessToken) jbody.access_token = jbody.accessToken
|
|
181
179
|
}
|
|
182
180
|
if (!jbody.access_token) {
|
|
183
|
-
this.lock.release()
|
|
184
181
|
const err = new Error(`[${action}] Error message: Retrieved invalid token response`)
|
|
185
182
|
throw (err)
|
|
186
183
|
}
|
|
@@ -229,7 +226,7 @@ export class HelperRest {
|
|
|
229
226
|
this._serviceClient[baseEntity][clientIdentifier].accessToken = accessToken
|
|
230
227
|
this._serviceClient[baseEntity][clientIdentifier].options.headers['Authorization'] = ` Bearer ${accessToken.access_token}`
|
|
231
228
|
} catch (err) {
|
|
232
|
-
delete this._serviceClient[baseEntity][clientIdentifier]
|
|
229
|
+
if (this._serviceClient[baseEntity]) delete this._serviceClient[baseEntity][clientIdentifier]
|
|
233
230
|
const newErr = err
|
|
234
231
|
throw newErr
|
|
235
232
|
}
|
|
@@ -517,9 +514,6 @@ export class HelperRest {
|
|
|
517
514
|
} catch (err: any) { // includes failover/retry logic based on config baseUrls array
|
|
518
515
|
let statusCode
|
|
519
516
|
try { statusCode = JSON.parse(err.message).statusCode } catch (e) { void 0 }
|
|
520
|
-
if (statusCode === 404) { // not logged as error, let caller decide e.g. getUser-manager
|
|
521
|
-
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
522
|
-
} else this.scimgateway.logError(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
523
517
|
const clientIdentifier = this.getClientIdentifier(ctx)
|
|
524
518
|
if (err.message.includes('ratelimit')) { // have seen throttling not follow standard 429/retry-after, but instead using 500 and error message only
|
|
525
519
|
if (!retryAfter) retryAfter = 60
|
|
@@ -527,13 +521,17 @@ export class HelperRest {
|
|
|
527
521
|
if (!retryCount) retryCount = 0
|
|
528
522
|
let urlObj
|
|
529
523
|
try { urlObj = new URL(path) } catch (err) { void 0 }
|
|
530
|
-
|
|
524
|
+
let isServiceClient = !urlObj && this._serviceClient[baseEntity] && this._serviceClient[baseEntity][clientIdentifier] && !this.lock.isLocked() // !isLocked to avoid retry ongoing doRequest with failing getAccessToken()
|
|
525
|
+
let oAuthTokeErr = statusCode === 401 && this.config_entity[baseEntity].connection?.auth?.type && this.config_entity[baseEntity].connection.auth.type.startsWith('oauth')
|
|
526
|
+
if (isServiceClient && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ABORT_ERR' || err.code === 'ETIMEDOUT' || oAuthTokeErr || retryAfter)) {
|
|
527
|
+
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
531
528
|
if (retryAfter) {
|
|
532
529
|
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} throttle/ratelimit error - awaiting ${retryAfter} seconds before automatic retry`)
|
|
533
530
|
await new Promise(resolve => setTimeout(function () {
|
|
534
531
|
resolve(null)
|
|
535
532
|
}, retryAfter * 1000))
|
|
536
533
|
}
|
|
534
|
+
if (oAuthTokeErr && this._serviceClient[baseEntity]) delete this._serviceClient[baseEntity][clientIdentifier] // ensure new getAccessToken request - token used should not have been expired, but rejected for other reason e.g. token server restart and no persistent token store?
|
|
537
535
|
if (retryCount < this.config_entity[baseEntity].connection.baseUrls.length) {
|
|
538
536
|
retryCount++
|
|
539
537
|
this.updateServiceClient(baseEntity, clientIdentifier, { baseUrl: this.config_entity[baseEntity].connection.baseUrls[retryCount - 1] })
|
|
@@ -541,13 +539,19 @@ export class HelperRest {
|
|
|
541
539
|
const ret = await this.doRequestHandler(baseEntity, method, path, body, ctx, opt, retryCount) // retry
|
|
542
540
|
return ret // problem fixed
|
|
543
541
|
} else {
|
|
542
|
+
if (statusCode === 404) { // not logged as error e.g. getUser-manager
|
|
543
|
+
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
544
|
+
} else this.scimgateway.logError(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} 11Error Response = ${err.message}`)
|
|
544
545
|
throw err
|
|
545
546
|
}
|
|
546
547
|
} else {
|
|
548
|
+
if (statusCode === 404) { // not logged as error e.g. getUser-manager
|
|
549
|
+
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
550
|
+
} else this.scimgateway.logError(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
547
551
|
if (statusCode === 401 && this._serviceClient[baseEntity]) {
|
|
548
552
|
delete this._serviceClient[baseEntity][clientIdentifier]
|
|
549
553
|
}
|
|
550
|
-
throw err //
|
|
554
|
+
throw err // Symantec IM retries getUser 6 times on ECONNREFUSED
|
|
551
555
|
}
|
|
552
556
|
}
|
|
553
557
|
}
|
package/lib/scimgateway.ts
CHANGED
|
@@ -698,7 +698,6 @@ export class ScimGateway {
|
|
|
698
698
|
}
|
|
699
699
|
if (tokenObj.baseEntities) {
|
|
700
700
|
if (Array.isArray(tokenObj.baseEntities) && tokenObj.baseEntities.length > 0) {
|
|
701
|
-
if (!baseEntity) return reject(new Error(`baseEntity=${baseEntity} not allowed for this bearerOAuth according to bearerOAuth configuration baseEntitites=${tokenObj.baseEntities}`))
|
|
702
701
|
if (!tokenObj.baseEntities.includes(baseEntity)) return reject(new Error(`baseEntity=${baseEntity} not allowed for this bearerOAuth according to bearerOAuth configuration baseEntitites=${tokenObj.baseEntities}`))
|
|
703
702
|
}
|
|
704
703
|
}
|
|
@@ -706,7 +705,11 @@ export class ScimGateway {
|
|
|
706
705
|
return resolve(true)
|
|
707
706
|
} else {
|
|
708
707
|
for (let i = 0; i < arr.length; i++) { // resolve if token memory store have been cleared because of a gateway restart
|
|
709
|
-
if (
|
|
708
|
+
if (arr[i].isTokenRequested || !arr[i].clientSecret) continue
|
|
709
|
+
if (arr[i].baseEntities && Array.isArray(arr[i].baseEntities) && arr[i].baseEntities.length > 0) {
|
|
710
|
+
if (!arr[i].baseEntities.includes(baseEntity)) continue
|
|
711
|
+
}
|
|
712
|
+
if (utils.getEncrypted(authToken, arr[i].clientSecret) === arr[i].clientSecret) {
|
|
710
713
|
arr[i].isTokenRequested = true // flagged as true to not allow repeated resolvements because token will also be cleared when expired
|
|
711
714
|
const baseEntities = utils.copyObj(arr[i].baseEntities)
|
|
712
715
|
let expires
|
|
@@ -774,8 +777,21 @@ export class ScimGateway {
|
|
|
774
777
|
if (ctx.request.url !== '/favicon.ico') logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${err.message}`)
|
|
775
778
|
return false
|
|
776
779
|
} catch (err: any) {
|
|
777
|
-
if (authType === 'Bearer')
|
|
778
|
-
|
|
780
|
+
if (authType === 'Bearer') {
|
|
781
|
+
let str = 'realm=""'
|
|
782
|
+
if (err?.name === 'invalid_token') {
|
|
783
|
+
str += `, error="${err.name}"`
|
|
784
|
+
if (err.message) {
|
|
785
|
+
str += `, error_description="${err.message}"`
|
|
786
|
+
const errMsg = {
|
|
787
|
+
error: err.name,
|
|
788
|
+
error_description: err.message,
|
|
789
|
+
}
|
|
790
|
+
ctx.response.body = JSON.stringify(errMsg)
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
ctx.response.headers.set('WWW-Authenticate', `Bearer ${str}`)
|
|
794
|
+
} else ctx.response.headers.set('WWW-Authenticate', 'Basic realm=""')
|
|
779
795
|
if (pwErrCount < 3) {
|
|
780
796
|
pwErrCount += 1
|
|
781
797
|
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message}`)
|
|
@@ -830,7 +846,20 @@ export class ScimGateway {
|
|
|
830
846
|
let jsonBody = ctx.request.body
|
|
831
847
|
try {
|
|
832
848
|
if (!jsonBody) throw new Error('missing body')
|
|
833
|
-
if (typeof jsonBody !== 'object')
|
|
849
|
+
if (typeof jsonBody !== 'object') { // might have application/x-www-form-urlencoded body, but incorrect Content-Type header
|
|
850
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] continue request validation even though incorrect body vs header Content-Type: ${ctx.request.headers.get('content-type')}`)
|
|
851
|
+
const arr = jsonBody.split('&')
|
|
852
|
+
const body: Record<string, any> = {}
|
|
853
|
+
arr.forEach((kv: string) => {
|
|
854
|
+
const a = kv.split('=')
|
|
855
|
+
if (a.length === 2) {
|
|
856
|
+
body[a[0]] = decodeURIComponent(a[1])
|
|
857
|
+
}
|
|
858
|
+
})
|
|
859
|
+
if (Object.keys(body).length < 1) throw new Error('body is not JSON nor application/x-www-form-urlencoded')
|
|
860
|
+
ctx.request.body = body // now json - ensure final info log will be masked
|
|
861
|
+
jsonBody = body
|
|
862
|
+
}
|
|
834
863
|
jsonBody = utils.copyObj(jsonBody) // no changes to original
|
|
835
864
|
} catch (err: any) {
|
|
836
865
|
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request error: ${err.message}`)
|
|
@@ -875,7 +904,7 @@ export class ScimGateway {
|
|
|
875
904
|
}
|
|
876
905
|
if (!token) {
|
|
877
906
|
err = 'invalid_client'
|
|
878
|
-
errDescr = 'incorrect or missing
|
|
907
|
+
errDescr = 'incorrect or missing client_id/client_secret'
|
|
879
908
|
if (pwErrCount < 3) {
|
|
880
909
|
pwErrCount += 1
|
|
881
910
|
} else { // delay brute force attempts
|
|
@@ -1008,7 +1037,6 @@ export class ScimGateway {
|
|
|
1008
1037
|
}
|
|
1009
1038
|
}
|
|
1010
1039
|
|
|
1011
|
-
userObj = utilsScim.addPrimaryAttrs(userObj)
|
|
1012
1040
|
scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
|
|
1013
1041
|
scimdata = utilsScim.addSchemas(scimdata, isScimv2, handle.description, undefined)
|
|
1014
1042
|
|
|
@@ -1259,7 +1287,6 @@ export class ScimGateway {
|
|
|
1259
1287
|
if (this.config.scimgateway.scim.skipMetaLocation) location = undefined
|
|
1260
1288
|
else if (ctx.query.attributes || (ctx.query.excludedAttributes && ctx.query.excludedAttributes.includes('meta'))) location = undefined
|
|
1261
1289
|
for (let i = 0; i < scimdata.Resources.length; i++) {
|
|
1262
|
-
scimdata.Resources[i] = utilsScim.addPrimaryAttrs(scimdata.Resources[i])
|
|
1263
1290
|
scimdata.Resources[i] = utils.stripObj(scimdata.Resources[i], ctx.query.attributes, ctx.query.excludedAttributes)
|
|
1264
1291
|
}
|
|
1265
1292
|
scimdata = utilsScim.addResources(scimdata, ctx.query.startIndex, ctx.query.sortBy, ctx.query.sortOrder)
|
|
@@ -1440,7 +1467,6 @@ export class ScimGateway {
|
|
|
1440
1467
|
ctx.response.headers.set('Location', location)
|
|
1441
1468
|
}
|
|
1442
1469
|
delete jsonBody.password
|
|
1443
|
-
jsonBody = utilsScim.addPrimaryAttrs(jsonBody)
|
|
1444
1470
|
jsonBody = utilsScim.addSchemas(jsonBody, isScimv2, handle.description, undefined)
|
|
1445
1471
|
ctx.response.status = 201
|
|
1446
1472
|
ctx.response.body = JSON.stringify(jsonBody)
|
|
@@ -1658,7 +1684,7 @@ export class ScimGateway {
|
|
|
1658
1684
|
const location = ctx.origin + ctx.path
|
|
1659
1685
|
ctx.response.headers.set('Location', location)
|
|
1660
1686
|
}
|
|
1661
|
-
const userObj =
|
|
1687
|
+
const userObj = scimdata.Resources[0]
|
|
1662
1688
|
scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
|
|
1663
1689
|
scimdata = utilsScim.addSchemas(scimdata, isScimv2, handle.description, undefined)
|
|
1664
1690
|
ctx.response.status = 200
|
|
@@ -2283,13 +2309,13 @@ export class ScimGateway {
|
|
|
2283
2309
|
body = JSON.parse(bodyString)
|
|
2284
2310
|
} catch (err: any) {
|
|
2285
2311
|
const contentType = request.headers.get('content-type')
|
|
2286
|
-
if (contentType && contentType.toLowerCase()
|
|
2312
|
+
if (contentType && contentType.toLowerCase().startsWith('application/x-www-form-urlencoded')) {
|
|
2287
2313
|
const arr = bodyString.split('&') // "grant_type=client_credentials&client_id=id&client_secret=secret"
|
|
2288
2314
|
body = {}
|
|
2289
2315
|
arr.forEach((kv: string) => {
|
|
2290
2316
|
const a = kv.split('=')
|
|
2291
2317
|
if (a.length === 2) {
|
|
2292
|
-
body[a[0]] = a[1]
|
|
2318
|
+
body[a[0]] = decodeURIComponent(a[1])
|
|
2293
2319
|
}
|
|
2294
2320
|
})
|
|
2295
2321
|
} else if (bodyString) body = bodyString
|
package/lib/utils-scim.ts
CHANGED
|
@@ -793,7 +793,19 @@ export function addSchemas(data: Record<string, any>, isScimv2: boolean, type?:
|
|
|
793
793
|
} else if (key === 'password') delete data.Resources[i].password // exclude password, null and empty object/array
|
|
794
794
|
else if (data.Resources[i][key] === null) delete data.Resources[i][key]
|
|
795
795
|
else if (JSON.stringify(data.Resources[i][key]) === '{}') delete data.Resources[i][key]
|
|
796
|
-
else if (Array.isArray(data.Resources[i][key])
|
|
796
|
+
else if (Array.isArray(data.Resources[i][key])) {
|
|
797
|
+
if (data.Resources[i][key].length < 1) delete data.Resources[i][key]
|
|
798
|
+
else if (key !== 'members' && key !== 'groups') { // any primary attribute should be boolean
|
|
799
|
+
for (let j = 0; j < data.Resources[i][key].length; j++) {
|
|
800
|
+
let el = data.Resources[i][key][j]
|
|
801
|
+
if (typeof el !== 'object') break
|
|
802
|
+
if (el.type && el.primary && typeof el.primary === 'string') {
|
|
803
|
+
if (el.primary.toLowerCase() === 'true') el.primary = true
|
|
804
|
+
else if (el.primary.toLowerCase() === 'false') el.primary = false
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
797
809
|
}
|
|
798
810
|
if (Object.keys(data.Resources[i]).length === 0) {
|
|
799
811
|
data.Resources.splice(i, 1) // delete
|
|
@@ -827,37 +839,25 @@ export function addSchemas(data: Record<string, any>, isScimv2: boolean, type?:
|
|
|
827
839
|
} else if (key === 'password') delete data.password // exclude password, null and empty object/array
|
|
828
840
|
else if (data[key] === null) delete data[key]
|
|
829
841
|
else if (JSON.stringify(data[key]) === '{}') delete data[key]
|
|
830
|
-
else if (Array.isArray(data[key])
|
|
842
|
+
else if (Array.isArray(data[key])) {
|
|
843
|
+
if (data[key].length < 1) delete data[key]
|
|
844
|
+
else if (key !== 'members' && key !== 'groups') { // any primary attribute should be boolean
|
|
845
|
+
for (let j = 0; j < data[key].length; j++) {
|
|
846
|
+
let el = data[key][j]
|
|
847
|
+
if (typeof el !== 'object') break
|
|
848
|
+
if (el.type && el.primary && typeof el.primary === 'string') {
|
|
849
|
+
if (el.primary.toLowerCase() === 'true') el.primary = true
|
|
850
|
+
else if (el.primary.toLowerCase() === 'false') el.primary = false
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
831
855
|
}
|
|
832
856
|
}
|
|
833
857
|
|
|
834
858
|
return data
|
|
835
859
|
}
|
|
836
860
|
|
|
837
|
-
// addPrimaryAttrs cheks for primary attributes (only for roles) and add them as standalone attributes
|
|
838
|
-
// some IdP's may check for these e.g. Azure
|
|
839
|
-
// e.g. {roles: [{value: "val1", primary: "True"}]}
|
|
840
|
-
// gives:
|
|
841
|
-
// { roles: [{value: "val1", primary: "True"}],
|
|
842
|
-
// roles[primary eq "True"].value: "val1",
|
|
843
|
-
// roles[primary eq "True"].primary: "True"}]
|
|
844
|
-
// }
|
|
845
|
-
export function addPrimaryAttrs(obj: Record<string, any>) {
|
|
846
|
-
const key = 'roles'
|
|
847
|
-
if (!obj || typeof obj !== 'object') return obj
|
|
848
|
-
if (!obj[key] || !Array.isArray(obj[key])) return obj
|
|
849
|
-
const o = utils.copyObj(obj)
|
|
850
|
-
const index = o[key].findIndex((el: Record<string, any>) => (el.primary === true || (typeof el.primary === 'string' && el.primary.toLowerCase() === 'true')))
|
|
851
|
-
if (index >= 0) {
|
|
852
|
-
const prim = o[key][index]
|
|
853
|
-
for (const k in prim) {
|
|
854
|
-
const primKey = `${key}[primary eq ${typeof prim.primary === 'string' ? `"${prim.primary}"` : prim.primary}].${k}` // roles[primary eq true].value / roles[primary eq "True"].value``
|
|
855
|
-
o[primKey] = prim[k] // { roles[primary eq true].value : "some-value" }
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return o
|
|
859
|
-
}
|
|
860
|
-
|
|
861
861
|
//
|
|
862
862
|
// SCIM error formatting
|
|
863
863
|
//
|
|
@@ -900,7 +900,7 @@ export function jsonErr(scimVersion: string | number, pluginName: string, htmlEr
|
|
|
900
900
|
|
|
901
901
|
if (scimVersion !== '2.0' && scimVersion !== 2) { // v1.1
|
|
902
902
|
errJson
|
|
903
|
-
|
|
903
|
+
= {
|
|
904
904
|
Errors: [
|
|
905
905
|
{
|
|
906
906
|
description: msg,
|
|
@@ -910,7 +910,7 @@ export function jsonErr(scimVersion: string | number, pluginName: string, htmlEr
|
|
|
910
910
|
}
|
|
911
911
|
} else { // v2.0
|
|
912
912
|
errJson
|
|
913
|
-
|
|
913
|
+
= {
|
|
914
914
|
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
|
915
915
|
scimType,
|
|
916
916
|
detail: msg,
|
package/lib/utils.ts
CHANGED
|
@@ -14,18 +14,20 @@ import fs from 'node:fs'
|
|
|
14
14
|
import path from 'node:path'
|
|
15
15
|
import { EventEmitter } from 'node:events'
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** Lock implements mutual exclusion
|
|
18
|
+
* reference: https://thecodebarbarian.com/mutual-exclusion-patterns-with-node-promises
|
|
19
|
+
*/
|
|
18
20
|
export class Lock {
|
|
19
|
-
private _locked
|
|
21
|
+
private _locked = false
|
|
20
22
|
private _ee: any
|
|
21
23
|
constructor() {
|
|
22
24
|
this._locked = false
|
|
23
25
|
this._ee = new EventEmitter()
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/** If nobody has the lock, take it and resolve immediately else wait until released */
|
|
26
29
|
acquire() {
|
|
27
30
|
return new Promise((resolve) => {
|
|
28
|
-
// If nobody has the lock, take it and resolve immediately
|
|
29
31
|
if (!this._locked) {
|
|
30
32
|
// Safe because JS doesn't interrupt you on synchronous operations,
|
|
31
33
|
// so no need for compare-and-swap or anything like that.
|
|
@@ -45,11 +47,16 @@ export class Lock {
|
|
|
45
47
|
})
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
/** Release the lock immediately */
|
|
48
51
|
release() {
|
|
49
|
-
// Release the lock immediately
|
|
50
52
|
this._locked = false
|
|
51
53
|
setImmediate(() => this._ee.emit('release'))
|
|
52
54
|
}
|
|
55
|
+
|
|
56
|
+
/** Return status of lock true/false */
|
|
57
|
+
isLocked(): boolean {
|
|
58
|
+
return this._locked
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
export const getSecret = function (dotNotationAttr: string, configFile: string) {
|
package/package.json
CHANGED