backtrace-service
Advanced tools
Comparing version 1.1.13 to 1.2.1
@@ -0,1 +1,10 @@ | ||
import { IServiceDescriptor } from '../model/descriptor'; | ||
export interface ICoronerDescriptor { | ||
name: string; | ||
server: string; | ||
secret: string; | ||
resource: string; | ||
proxy: boolean; | ||
enabled: boolean; | ||
} | ||
/** | ||
@@ -11,3 +20,14 @@ * getBackupConfig fetches a config file provided by the service itself. | ||
export declare function getProperConfig(serviceName: string): object | undefined; | ||
export interface IDescriptorOpts { | ||
server?: string; | ||
resource?: string; | ||
proxy?: boolean; | ||
} | ||
/** | ||
* getDescriptor fetches the service's integration parameters. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
* @param defaultPort - the default port number for the service. | ||
*/ | ||
export declare function getDescriptor(serviceName: string, defaultPort: number, opts?: IDescriptorOpts): IServiceDescriptor; | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
@@ -14,0 +34,0 @@ * If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path |
@@ -12,2 +12,5 @@ "use strict"; | ||
var log = __importStar(require("../log/log")); | ||
var crypto_1 = require("crypto"); | ||
var url = __importStar(require("url")); | ||
; | ||
/** | ||
@@ -45,3 +48,57 @@ * getBackupConfig fetches a config file provided by the service itself. | ||
exports.getProperConfig = getProperConfig; | ||
; | ||
/** | ||
* getDescriptor fetches the service's integration parameters. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
* @param defaultPort - the default port number for the service. | ||
*/ | ||
function getDescriptor(serviceName, defaultPort, opts) { | ||
if (opts === void 0) { opts = {}; } | ||
var varprefix = process.env.SERVICE_DESCRIPTOR_VARPREFIX || | ||
'/var/run/coronerd/services.d'; | ||
var varpath = process.env.SERVICE_DESCRIPTOR_VARPATH || | ||
varprefix + "/" + serviceName + ".json"; | ||
var descr = {}; | ||
var paths = []; | ||
var i = 0; | ||
if (!process.env.SERVICE_DESCRIPTOR_PATH) { | ||
paths.push("/etc/coronerd/services.d/" + serviceName + ".json"); | ||
paths.push(varpath); | ||
} | ||
else { | ||
paths.push(process.env.SERVICE_DESCRIPTOR_PATH); | ||
} | ||
for (; i < paths.length; i++) { | ||
try { | ||
descr = JSON.parse(fs.readFileSync(paths[i], { encoding: 'utf8' })); | ||
break; | ||
} | ||
catch (error) { | ||
} | ||
} | ||
if (i === paths.length) { | ||
log.info("Generating service integration for " + serviceName + " in " + varpath); | ||
descr.name = serviceName; | ||
descr.secret = crypto_1.randomBytes(32).toString('hex'); | ||
descr.server = opts.server || "https://0.0.0.0:" + defaultPort; | ||
descr.resource = opts.resource || "/api/" + serviceName; | ||
descr.proxy = opts.proxy === false ? false : true; | ||
descr.enabled = true; | ||
fs.writeFileSync(varpath, JSON.stringify(descr, null, 2)); | ||
} | ||
var surl = url.parse(descr.server); | ||
if (!surl.port && surl.protocol !== 'https:') | ||
throw new Error("unspecified port number requires https"); | ||
var port = 443; | ||
if (surl.port) | ||
port = parseInt(surl.port); | ||
return { | ||
name: descr.name, | ||
resource: descr.resource, | ||
secret: descr.secret, | ||
port: port, | ||
}; | ||
} | ||
exports.getDescriptor = getDescriptor; | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
@@ -48,0 +105,0 @@ * If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path |
import { NextFunction, Request, Response } from 'express'; | ||
import { ICoronerRequestOption } from './model/authRequestOptions'; | ||
import { IServerConfiguration } from './model/serverConfiguration'; | ||
import { IServiceDescriptor } from '../model/descriptor'; | ||
/** | ||
@@ -8,12 +9,8 @@ * Identity manager | ||
export declare class IdentityManager { | ||
name: string; | ||
secret: string; | ||
logger: { | ||
log: (level: string, log: string) => void; | ||
} | undefined; | ||
constructor(name: string, secret: string); | ||
private descr; | ||
private logger; | ||
constructor(descr: IServiceDescriptor); | ||
setLogger(logger: { | ||
log: (level: string, msg: string) => void; | ||
}): void; | ||
private log; | ||
serviceRequest(opts: ICoronerRequestOption): ((request: Request, response: Response, next: NextFunction) => void)[]; | ||
@@ -28,2 +25,7 @@ /** | ||
/** | ||
* Login to a coronerd. | ||
* @param Coronerd base URL to login to | ||
*/ | ||
loginCoronerd(coronerdUrl: string): Promise<IServerConfiguration | undefined>; | ||
/** | ||
* Check if authtoken is a valid token generated by coronerd | ||
@@ -35,7 +37,2 @@ * @param universeUrl url to coronerd universe | ||
/** | ||
* Login to a coronerd. | ||
* @param Coronerd base URL to login to | ||
*/ | ||
loginCoronerd(coronerdUrl: string): Promise<IServerConfiguration | undefined>; | ||
/** | ||
* Get Backtrace configuration | ||
@@ -48,2 +45,4 @@ * @param universeUrl url to coronerd universe | ||
private checkHmac; | ||
private log; | ||
private readCoronerAuthInfo; | ||
} |
@@ -49,16 +49,12 @@ "use strict"; | ||
var IdentityManager = /** @class */ (function () { | ||
function IdentityManager(name, secret) { | ||
this.name = name; | ||
this.secret = secret; | ||
function IdentityManager(descr) { | ||
this.descr = descr; | ||
this.logger = undefined; | ||
} | ||
IdentityManager.prototype.setLogger = function (logger) { | ||
if (!logger || !logger.log) | ||
if (!logger || !logger.log) { | ||
throw new TypeError('unusable logger passed in'); | ||
} | ||
this.logger = logger; | ||
}; | ||
IdentityManager.prototype.log = function (level, msg) { | ||
if (this.logger) | ||
this.logger.log(level, msg); | ||
}; | ||
/* | ||
@@ -94,3 +90,3 @@ * Generate a middleware handler for Express.js, using options to hook the | ||
} | ||
if (!_this.checkHmac(_this.secret, nonce, hmac)) { | ||
if (!_this.checkHmac(_this.descr.secret, nonce, hmac)) { | ||
responseResult_1.ResponseResult.unauthorized(response); | ||
@@ -123,11 +119,3 @@ return; | ||
return function (request, response, next) { | ||
var data = request.coronerAuth; | ||
if (!data) { | ||
data = { | ||
token: request.get('X-Coroner-Token'), | ||
url: request.get('X-Coroner-Location') || request.get('origin'), | ||
}; | ||
request.coronerAuth = data; | ||
} | ||
var url = data.url, token = data.token; | ||
var _a = _this.readCoronerAuthInfo(request), url = _a.url, token = _a.token; | ||
if (!token || !url) { | ||
@@ -138,3 +126,2 @@ _this.log('error', request.ip + ": missing internal params token: " + token + " || url: " + url); | ||
} | ||
// let result: AxiosResponse<IServerConfiguration>; | ||
var prefix = url.endsWith('/') ? '' : '/'; | ||
@@ -144,5 +131,5 @@ axios_1.default | ||
headers: { | ||
'X-Service-Name': _this.name, | ||
'X-Service-Name': _this.descr.name, | ||
'X-Coroner-Token': token, | ||
'X-Service-HMAC': _this.generateHmac(_this.secret, token), | ||
'X-Service-HMAC': _this.generateHmac(_this.descr.secret, token), | ||
}, | ||
@@ -159,3 +146,3 @@ }) | ||
if (!!coronerd_nonce && !!coronerd_hmac) { | ||
if (!_this.checkHmac(_this.secret, coronerd_nonce, coronerd_hmac)) { | ||
if (!_this.checkHmac(_this.descr.secret, coronerd_nonce, coronerd_hmac)) { | ||
responseResult_1.ResponseResult.badRequest(response, 'missing parameters'); | ||
@@ -184,20 +171,2 @@ next(new Error('Invalid server generated HMAC')); | ||
/** | ||
* Check if authtoken is a valid token generated by coronerd | ||
* @param universeUrl url to coronerd universe | ||
* @param authToken user token | ||
*/ | ||
IdentityManager.prototype.isValidToken = function (universeUrl, authToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.getConfiguration(universeUrl, authToken)]; | ||
case 1: | ||
result = _a.sent(); | ||
return [2 /*return*/, result !== undefined]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Login to a coronerd. | ||
@@ -208,12 +177,16 @@ * @param Coronerd base URL to login to | ||
return __awaiter(this, void 0, void 0, function () { | ||
var requestUrl, nonce, hmac, result; | ||
var prefix, requestUrl, nonce, hmac, result, err_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
requestUrl = coronerdUrl + "/api/login"; | ||
prefix = coronerdUrl.endsWith('/') ? '' : '/'; | ||
requestUrl = "" + coronerdUrl + prefix + "api/login"; | ||
nonce = crypto_1.randomBytes(32).toString('hex'); | ||
hmac = this.generateHmac(this.secret, nonce); | ||
hmac = this.generateHmac(this.descr.secret, nonce); | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, axios_1.default.post(requestUrl, null, { | ||
headers: { | ||
'X-Service-Name': this.name, | ||
'X-Service-Name': this.descr.name, | ||
'X-Service-Nonce': nonce, | ||
@@ -223,5 +196,10 @@ 'X-Service-HMAC': hmac, | ||
})]; | ||
case 1: | ||
case 2: | ||
result = _a.sent(); | ||
return [2 /*return*/, result.status === 200 ? result.data : undefined]; | ||
case 3: | ||
err_1 = _a.sent(); | ||
this.log('error', "Cannot login to coronerd. Reason " + JSON.stringify(err_1)); | ||
return [2 /*return*/, undefined]; | ||
case 4: return [2 /*return*/]; | ||
} | ||
@@ -232,2 +210,20 @@ }); | ||
/** | ||
* Check if authtoken is a valid token generated by coronerd | ||
* @param universeUrl url to coronerd universe | ||
* @param authToken user token | ||
*/ | ||
IdentityManager.prototype.isValidToken = function (universeUrl, authToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.getConfiguration(universeUrl, authToken)]; | ||
case 1: | ||
result = _a.sent(); | ||
return [2 /*return*/, result !== undefined]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Get Backtrace configuration | ||
@@ -239,3 +235,3 @@ * @param universeUrl url to coronerd universe | ||
return __awaiter(this, void 0, void 0, function () { | ||
var prefix, requestUrl, result, err_1; | ||
var prefix, requestUrl, result, err_2; | ||
return __generator(this, function (_a) { | ||
@@ -261,3 +257,3 @@ switch (_a.label) { | ||
case 3: | ||
err_1 = _a.sent(); | ||
err_2 = _a.sent(); | ||
return [2 /*return*/, undefined]; | ||
@@ -277,2 +273,18 @@ case 4: return [2 /*return*/]; | ||
}; | ||
IdentityManager.prototype.log = function (level, msg) { | ||
if (this.logger) { | ||
this.logger.log(level, msg); | ||
} | ||
}; | ||
IdentityManager.prototype.readCoronerAuthInfo = function (request) { | ||
var data = request.coronerAuth; | ||
if (!data) { | ||
data = { | ||
token: request.get('X-Coroner-Token'), | ||
url: request.get('X-Coroner-Location') || request.get('origin'), | ||
}; | ||
request.coronerAuth = data; | ||
} | ||
return data; | ||
}; | ||
return IdentityManager; | ||
@@ -279,0 +291,0 @@ }()); |
@@ -14,2 +14,2 @@ /** | ||
export { UniverseHelper } from './universe/backtraceUniverseHelper'; | ||
export { getBackupConfig, getConfig, getProperConfig } from './config/config'; | ||
export { getBackupConfig, getConfig, getDescriptor, getProperConfig } from './config/config'; |
@@ -23,3 +23,4 @@ "use strict"; | ||
exports.getConfig = config_1.getConfig; | ||
exports.getDescriptor = config_1.getDescriptor; | ||
exports.getProperConfig = config_1.getProperConfig; | ||
//# sourceMappingURL=index.js.map |
@@ -8,4 +8,4 @@ import { Response } from 'express'; | ||
static ok(response: Response): void; | ||
static internalError(response: Response, msg?: string): void; | ||
static internalError(response: Response, msg: string): void; | ||
constructor(msg: string, status?: string); | ||
} |
@@ -20,3 +20,5 @@ "use strict"; | ||
}; | ||
ResponseResult.internalError = function (response, msg) { }; | ||
ResponseResult.internalError = function (response, msg) { | ||
response.status(500).send(new ResponseResult(msg, 'error')); | ||
}; | ||
return ResponseResult; | ||
@@ -23,0 +25,0 @@ }()); |
{ | ||
"name": "backtrace-service", | ||
"version": "1.1.13", | ||
"version": "1.2.1", | ||
"description": "Common tools for Backtrace Node services", | ||
@@ -52,8 +52,5 @@ "author": "Backtrace", | ||
"nock": "^10.0.6", | ||
"prettier": "^1.17.0", | ||
"ts-jest": "^24.0.2", | ||
"ts-node": "^8.0.3", | ||
"tslint-config-prettier": "^1.18.0", | ||
"typescript": "^3.4.4" | ||
} | ||
} |
@@ -248,2 +248,17 @@ # Backtrace Service Layer nodejs library | ||
#### GetDescriptor | ||
A service can obtain its service integration settings by using | ||
`getDescriptor`, which returns an IServiceDescriptor. It must specify a | ||
default port number, to be used in on-premise configurations. It may | ||
specify additional default parameters as IDescriptorOpts. | ||
The values returned in the IServiceDescriptor must be used by the service in | ||
order to properly integrate. This function generates a descriptor file, if | ||
necessary, otherwise it uses what's provided. | ||
```javascript | ||
const descriptor = getDescriptor('service-name', 12345); | ||
``` | ||
### GetConfig | ||
@@ -250,0 +265,0 @@ |
import * as fs from 'fs'; | ||
import * as log from '../log/log'; | ||
import { randomBytes } from 'crypto'; | ||
import * as url from 'url'; | ||
import { IServiceDescriptor } from '../model/descriptor'; | ||
/* What coronerd sees; not intended for service consumption. */ | ||
export interface ICoronerDescriptor { | ||
name: string, | ||
server: string, | ||
secret: string, | ||
resource: string, | ||
proxy: boolean, | ||
enabled: boolean, | ||
}; | ||
/** | ||
@@ -37,4 +50,68 @@ * getBackupConfig fetches a config file provided by the service itself. | ||
export interface IDescriptorOpts { | ||
server?: string, | ||
resource?: string, | ||
proxy?: boolean, | ||
}; | ||
/** | ||
* getDescriptor fetches the service's integration parameters. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
* @param defaultPort - the default port number for the service. | ||
*/ | ||
export function getDescriptor(serviceName: string, defaultPort: number, | ||
opts: IDescriptorOpts = {}): IServiceDescriptor { | ||
const varprefix = process.env.SERVICE_DESCRIPTOR_VARPREFIX || | ||
'/var/run/coronerd/services.d'; | ||
const varpath = process.env.SERVICE_DESCRIPTOR_VARPATH || | ||
`${varprefix}/${serviceName}.json`; | ||
let descr: ICoronerDescriptor = <ICoronerDescriptor> {}; | ||
let paths = []; | ||
let i = 0; | ||
if (!process.env.SERVICE_DESCRIPTOR_PATH) { | ||
paths.push(`/etc/coronerd/services.d/${serviceName}.json`); | ||
paths.push(varpath); | ||
} else { | ||
paths.push(process.env.SERVICE_DESCRIPTOR_PATH); | ||
} | ||
for (; i < paths.length; i++) { | ||
try { | ||
descr = JSON.parse(fs.readFileSync(paths[i], { encoding: 'utf8' })); | ||
break; | ||
} catch (error) { | ||
} | ||
} | ||
if (i === paths.length) { | ||
log.info(`Generating service integration for ${serviceName} in ${varpath}`); | ||
descr.name = serviceName; | ||
descr.secret = randomBytes(32).toString('hex'); | ||
descr.server = opts.server || `https://0.0.0.0:${defaultPort}`; | ||
descr.resource = opts.resource || `/api/${serviceName}`; | ||
descr.proxy = opts.proxy === false ? false : true; | ||
descr.enabled = true; | ||
fs.writeFileSync(varpath, JSON.stringify(descr, null, 2)); | ||
} | ||
const surl = url.parse(descr.server); | ||
if (!surl.port && surl.protocol !== 'https:') | ||
throw new Error("unspecified port number requires https"); | ||
let port: number = 443; | ||
if (surl.port) | ||
port = parseInt(surl.port); | ||
return { | ||
name: descr.name, | ||
resource: descr.resource, | ||
secret: descr.secret, | ||
port: port, | ||
}; | ||
} | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
@@ -51,2 +128,2 @@ * If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path | ||
return config; | ||
} | ||
} |
@@ -12,2 +12,3 @@ import axios from 'axios'; | ||
import { IServerConfiguration } from './model/serverConfiguration'; | ||
import { IServiceDescriptor } from '../model/descriptor'; | ||
@@ -18,23 +19,13 @@ /** | ||
export class IdentityManager { | ||
name: string; | ||
secret: string; | ||
logger: { log: (level: string, log: string) => void } | undefined; | ||
private logger: { log: (level: string, log: string) => void } | undefined = undefined; | ||
constructor(name: string, secret: string) { | ||
this.name = name; | ||
this.secret = secret; | ||
this.logger = undefined; | ||
} | ||
constructor(private descr: IServiceDescriptor) {} | ||
public setLogger(logger: { log: (level: string, msg: string) => void }) { | ||
if (!logger || !logger.log) | ||
if (!logger || !logger.log) { | ||
throw new TypeError('unusable logger passed in'); | ||
} | ||
this.logger = logger; | ||
} | ||
private log(level: string, msg: string) { | ||
if (this.logger) | ||
this.logger.log(level, msg); | ||
} | ||
/* | ||
@@ -78,3 +69,3 @@ * Generate a middleware handler for Express.js, using options to hook the | ||
if (!this.checkHmac(this.secret, nonce, hmac)) { | ||
if (!this.checkHmac(this.descr.secret, nonce, hmac)) { | ||
ResponseResult.unauthorized(response); | ||
@@ -110,11 +101,3 @@ return; | ||
return (request: Request, response: Response, next: NextFunction) => { | ||
let data = (request as any).coronerAuth as ICoronerAuth; | ||
if (!data) { | ||
data = { | ||
token: request.get('X-Coroner-Token') as string, | ||
url: (request.get('X-Coroner-Location') as string) || (request.get('origin') as string), | ||
}; | ||
(request as any).coronerAuth = data; | ||
} | ||
const { url, token } = data; | ||
const { url, token } = this.readCoronerAuthInfo(request); | ||
if (!token || !url) { | ||
@@ -126,3 +109,2 @@ this.log('error', `${request.ip}: missing internal params token: ${token} || url: ${url}`); | ||
// let result: AxiosResponse<IServerConfiguration>; | ||
const prefix = url.endsWith('/') ? '' : '/'; | ||
@@ -132,5 +114,5 @@ axios | ||
headers: { | ||
'X-Service-Name': this.name, | ||
'X-Service-Name': this.descr.name, | ||
'X-Coroner-Token': token, | ||
'X-Service-HMAC': this.generateHmac(this.secret, token), | ||
'X-Service-HMAC': this.generateHmac(this.descr.secret, token), | ||
}, | ||
@@ -148,3 +130,3 @@ }) | ||
if (!!coronerd_nonce && !!coronerd_hmac) { | ||
if (!this.checkHmac(this.secret, coronerd_nonce, coronerd_hmac)) { | ||
if (!this.checkHmac(this.descr.secret, coronerd_nonce, coronerd_hmac)) { | ||
ResponseResult.badRequest(response, 'missing parameters'); | ||
@@ -174,2 +156,26 @@ next(new Error('Invalid server generated HMAC')); | ||
/** | ||
* Login to a coronerd. | ||
* @param Coronerd base URL to login to | ||
*/ | ||
public async loginCoronerd(coronerdUrl: string): Promise<IServerConfiguration | undefined> { | ||
const prefix = coronerdUrl.endsWith('/') ? '' : '/'; | ||
const requestUrl = `${coronerdUrl}${prefix}api/login`; | ||
const nonce = randomBytes(32).toString('hex'); | ||
const hmac = this.generateHmac(this.descr.secret, nonce); | ||
try { | ||
const result = await axios.post<IServerConfiguration>(requestUrl, null, { | ||
headers: { | ||
'X-Service-Name': this.descr.name, | ||
'X-Service-Nonce': nonce, | ||
'X-Service-HMAC': hmac, | ||
}, | ||
}); | ||
return result.status === 200 ? result.data : undefined; | ||
} catch (err) { | ||
this.log('error', `Cannot login to coronerd. Reason ${JSON.stringify(err)}`); | ||
return undefined; | ||
} | ||
} | ||
/** | ||
* Check if authtoken is a valid token generated by coronerd | ||
@@ -185,22 +191,2 @@ * @param universeUrl url to coronerd universe | ||
/** | ||
* Login to a coronerd. | ||
* @param Coronerd base URL to login to | ||
*/ | ||
public async loginCoronerd( | ||
coronerdUrl: string | ||
): Promise<IServerConfiguration | undefined> { | ||
const requestUrl = `${coronerdUrl}/api/login`; | ||
const nonce = randomBytes(32).toString('hex'); | ||
const hmac = this.generateHmac(this.secret, nonce); | ||
const result = await axios.post<IServerConfiguration>(requestUrl, null, { | ||
headers: { | ||
'X-Service-Name': this.name, | ||
'X-Service-Nonce': nonce, | ||
'X-Service-HMAC': hmac, | ||
}, | ||
}); | ||
return result.status === 200 ? result.data : undefined; | ||
} | ||
/** | ||
* Get Backtrace configuration | ||
@@ -240,2 +226,19 @@ * @param universeUrl url to coronerd universe | ||
} | ||
private log(level: string, msg: string) { | ||
if (this.logger) { | ||
this.logger.log(level, msg); | ||
} | ||
} | ||
private readCoronerAuthInfo(request: Request) { | ||
let data = (request as any).coronerAuth as ICoronerAuth; | ||
if (!data) { | ||
data = { | ||
token: request.get('X-Coroner-Token') as string, | ||
url: (request.get('X-Coroner-Location') as string) || (request.get('origin') as string), | ||
}; | ||
(request as any).coronerAuth = data; | ||
} | ||
return data; | ||
} | ||
} |
@@ -16,2 +16,2 @@ /** | ||
export { UniverseHelper } from './universe/backtraceUniverseHelper'; | ||
export { getBackupConfig, getConfig, getProperConfig } from './config/config'; | ||
export { getBackupConfig, getConfig, getDescriptor, getProperConfig } from './config/config'; |
@@ -16,5 +16,7 @@ import { Response } from 'express'; | ||
public static internalError(response: Response, msg?: string): void {} | ||
public static internalError(response: Response, msg: string): void { | ||
response.status(500).send(new ResponseResult(msg, 'error')); | ||
} | ||
constructor(public readonly msg: string, public status: string = 'error') {} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81248
8
39
1350
317
9