New Drive UI initial version
This commit is contained in:
@@ -8,37 +8,37 @@
|
||||
"mobile_appstore": "https://apps.apple.com/fr/app/twake/id1588764852?l=en",
|
||||
"mobile_googleplay": "https://play.google.com/store/apps/details?id=com.twake.twake&gl=FR"
|
||||
},
|
||||
"app_grid": [
|
||||
{
|
||||
"name": "Tmail",
|
||||
"logo": "/public/img/grid/tmail.png",
|
||||
"url": "https://tmail.linagora.com/"
|
||||
},
|
||||
{
|
||||
"name": "Twake",
|
||||
"logo": "/public/img/grid/twake.png",
|
||||
"url": "https://twake.app/"
|
||||
}
|
||||
],
|
||||
"accounts": {
|
||||
"_type": "console",
|
||||
"_type": "remote",
|
||||
"type": "internal",
|
||||
"internal": {
|
||||
"disable_account_creation": false
|
||||
},
|
||||
"console": {
|
||||
"account_management_url": "http://web.twake-console.local/profile?company-code={company_id}",
|
||||
"remote": {
|
||||
"authority": "http://auth.example.com/",
|
||||
"client_id": "twakeweb",
|
||||
"client_secret": "",
|
||||
"issuer": "",
|
||||
"audience": "",
|
||||
"redirect_uris": [""],
|
||||
"account_management_url": "http://web.twake-console.local/profile?company-code={company_id}",
|
||||
"collaborators_management_url": "http://web.twake-console.local/compaies/{company_id}/users?company-code={company_id}",
|
||||
"company_management_url": "http://web.twake-console.local/companies?company-code={company_id}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"console": {
|
||||
"_type": "remote",
|
||||
"type": "internal",
|
||||
"remote": {
|
||||
"new_console": false,
|
||||
"username": "",
|
||||
"password": "12345678",
|
||||
"url": "https://some-remote-console-url/",
|
||||
"hook": {
|
||||
"token": "12345678"
|
||||
}
|
||||
},
|
||||
"internal": {
|
||||
"disable_account_creation": false
|
||||
}
|
||||
},
|
||||
"sentry": {
|
||||
"dsn": ""
|
||||
},
|
||||
@@ -63,10 +63,6 @@
|
||||
"root": "./public"
|
||||
}
|
||||
},
|
||||
"phpnode": {
|
||||
"secret": "api_supersecret",
|
||||
"php_endpoint": "http://nginx:80"
|
||||
},
|
||||
"websocket": {
|
||||
"path": "/socket/",
|
||||
"adapters": {
|
||||
@@ -154,7 +150,6 @@
|
||||
"api_key": "secret",
|
||||
"sender": "noreply@twake.app"
|
||||
},
|
||||
"applications": [],
|
||||
"services": [
|
||||
"auth",
|
||||
"push",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"@fastify/formbody": "^6.0.0",
|
||||
"@fastify/static": "^5.0.1",
|
||||
"@ffprobe-installer/ffprobe": "^1.4.1",
|
||||
"@okta/jwt-verifier": "^3.0.1",
|
||||
"@sentry/node": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@socket.io/redis-adapter": "^7.1.0",
|
||||
@@ -160,6 +161,7 @@
|
||||
"get-website-favicon": "^0.0.7",
|
||||
"html-metadata-parser": "^2.0.4",
|
||||
"html-to-text": "^8.2.1",
|
||||
"idtoken-verifier": "^2.2.3",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"keyv": "^4.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -172,6 +174,7 @@
|
||||
"node-cron": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-uuid": "^1.4.8",
|
||||
"openid-client": "^5.4.0",
|
||||
"ora": "^5.4.0",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pdf2pic": "^2.1.4",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { CommandModule } from "yargs";
|
||||
|
||||
const command: CommandModule = {
|
||||
describe: "Manage Twake Console",
|
||||
command: "console <command>",
|
||||
builder: yargs =>
|
||||
yargs.commandDir("console_cmds", {
|
||||
visit: commandModule => commandModule.default,
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
handler: () => {},
|
||||
};
|
||||
|
||||
export default command;
|
||||
@@ -1,257 +0,0 @@
|
||||
import yargs from "yargs";
|
||||
import ora from "ora";
|
||||
import twake from "../../../twake";
|
||||
import { CompanyReport, UserReport } from "../../../services/console/types";
|
||||
import Company from "../../../services/user/entities/company";
|
||||
import gr from "../../../services/global-resolver";
|
||||
import { ConsoleServiceImpl } from "../../../services/console/service";
|
||||
|
||||
/**
|
||||
* Merge command parameters. Check the builder definition below for more details.
|
||||
*/
|
||||
type MergeParams = {
|
||||
url: string;
|
||||
concurrent: number;
|
||||
dry: boolean;
|
||||
console: string;
|
||||
link: boolean;
|
||||
csv: boolean;
|
||||
client: string;
|
||||
secret: string;
|
||||
};
|
||||
|
||||
const services = [
|
||||
"platform-services",
|
||||
"user",
|
||||
"channels",
|
||||
"notifications",
|
||||
"database",
|
||||
"webserver",
|
||||
"message-queue",
|
||||
"console",
|
||||
];
|
||||
|
||||
const command: yargs.CommandModule<MergeParams, MergeParams> = {
|
||||
command: "merge",
|
||||
describe: "Merge Twake Chat users in the Twake Console",
|
||||
builder: {
|
||||
url: {
|
||||
default: "http://localhost:8080",
|
||||
type: "string",
|
||||
description: "URL of the Twake console",
|
||||
},
|
||||
concurrent: {
|
||||
default: 1,
|
||||
type: "number",
|
||||
description: "Number of concurrent imports",
|
||||
},
|
||||
dry: {
|
||||
default: false,
|
||||
type: "boolean",
|
||||
description: "Make a dry run without creating anything on the Twake console",
|
||||
},
|
||||
csv: {
|
||||
default: false,
|
||||
type: "boolean",
|
||||
description: "Generate result as CSV",
|
||||
},
|
||||
console: {
|
||||
default: "console",
|
||||
type: "string",
|
||||
description: "The console service identifier to user to link user and companies",
|
||||
},
|
||||
link: {
|
||||
default: true,
|
||||
type: "boolean",
|
||||
description:
|
||||
"Link the companies/users to external companies/user. Works with the --console parameter",
|
||||
implies: "console",
|
||||
},
|
||||
client: {
|
||||
default: "twake",
|
||||
type: "string",
|
||||
description: "Client identifier to be used to authenticate calls to the Console",
|
||||
},
|
||||
secret: {
|
||||
default: "secret",
|
||||
type: "string",
|
||||
description: "Client secret to be used to authenticate calls to the Console",
|
||||
},
|
||||
},
|
||||
handler: async argv => {
|
||||
const spinner = ora({ text: `Importing Twake data on ${argv.url}` }).start();
|
||||
const platform = await twake.run(services);
|
||||
await gr.doInit(platform);
|
||||
const consoleService = platform.getProvider<ConsoleServiceImpl>("console");
|
||||
const merge = consoleService.merge(
|
||||
argv.url,
|
||||
argv.concurrent,
|
||||
argv.dry,
|
||||
argv.console,
|
||||
argv.link,
|
||||
argv.client,
|
||||
argv.secret,
|
||||
);
|
||||
const start = Date.now();
|
||||
let stop: number = start;
|
||||
const userReports: UserReport[] = [];
|
||||
const companyReports: CompanyReport[] = [];
|
||||
const ownerReports: UserReport[] = [];
|
||||
let companiesWithoutAdmin: Company[] = [];
|
||||
|
||||
const process = merge.subscribe({
|
||||
next: report => {
|
||||
if (report.type === "company:created") {
|
||||
const companyReport = report.data as CompanyReport;
|
||||
companyReports.push(companyReport);
|
||||
|
||||
if (companyReport.status === "success") {
|
||||
spinner.succeed(
|
||||
`Company created: ${companyReport.company.source.id} ${companyReport.company.source.displayName}`,
|
||||
);
|
||||
} else {
|
||||
spinner.fail(
|
||||
`Creation error for company ${companyReport.company.source.id} (${companyReport.company.source.displayName}): ${companyReport?.error?.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.type === "user:created") {
|
||||
const userReport = report.data as UserReport;
|
||||
userReports.push(userReport);
|
||||
|
||||
if (userReport.status === "success") {
|
||||
spinner.succeed(
|
||||
`Company ${userReport.user.source.company.id}: User created ${userReport.user.destination.id}`,
|
||||
);
|
||||
} else {
|
||||
spinner.fail(
|
||||
`Company ${userReport.user.source.company.id}: User creation error ${userReport.user.destination.id}: ${userReport?.error?.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.type === "user:updated") {
|
||||
const userReport = report.data as UserReport;
|
||||
ownerReports.push(userReport);
|
||||
if (userReport.status === "success") {
|
||||
spinner.succeed(
|
||||
`Company ${userReport.destinationCompanyCode} owner updated to user ${userReport.destinationId}`,
|
||||
);
|
||||
} else {
|
||||
spinner.fail(
|
||||
`Company ${userReport.destinationCompanyCode}: Owner update error for user ${userReport.destinationId}: ${userReport?.error?.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.type === "processing:owner") {
|
||||
spinner.start("Computing company owners, please wait...");
|
||||
}
|
||||
|
||||
if (report.type === "user:updating") {
|
||||
//spinner.succeed(`Updating owner for company ${report?.company?.sourceId}`);
|
||||
}
|
||||
|
||||
if (report.type === "log") {
|
||||
report.message && (spinner.text = report.message);
|
||||
}
|
||||
|
||||
if (report.type === "company:withoutadmin") {
|
||||
report.message && (spinner.text = report.message);
|
||||
const companies = (report.data || []) as Company[];
|
||||
|
||||
if (companies.length) {
|
||||
companiesWithoutAdmin = companies;
|
||||
}
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
spinner.fail("Fatal error");
|
||||
},
|
||||
complete: async () => {
|
||||
stop = Date.now();
|
||||
spinner.succeed("Merge is complete");
|
||||
displayStats();
|
||||
await tearDown();
|
||||
},
|
||||
});
|
||||
|
||||
async function tearDown() {
|
||||
await platform.stop();
|
||||
process.unsubscribe();
|
||||
}
|
||||
|
||||
function reportAsCSV() {
|
||||
const users = userReports.map(
|
||||
user =>
|
||||
`${user.sourceId},${user.destinationId},${user.destinationCompanyCode},${user.status}`,
|
||||
);
|
||||
const userCSV = [...["sourceId,destinationId,destinationCompanyCode,status"], ...users].join(
|
||||
"\n",
|
||||
);
|
||||
|
||||
const companies = companyReports.map(
|
||||
company => `${company.sourceId},${company.destinationCode},${company.status}`,
|
||||
);
|
||||
const companyCSV = [...["sourceId,destinationCode,status"], ...companies].join("\n");
|
||||
|
||||
console.log(userCSV);
|
||||
console.log(companyCSV);
|
||||
}
|
||||
|
||||
function displayStats() {
|
||||
const userFailures = userReports.filter(user => user.error);
|
||||
const companyFailures = companyReports.filter(company => company.error);
|
||||
const ownerFailures = ownerReports.filter(report => report.error);
|
||||
|
||||
console.log("# Import report");
|
||||
console.log(`Data imported in ${(stop - start) / 1000} seconds`);
|
||||
console.log("## Company");
|
||||
console.log("- Companies success:", companyReports.filter(company => !company.error).length);
|
||||
console.log("- Companies failure:", companyFailures.length);
|
||||
if (companyFailures.length) {
|
||||
console.log("### Failures");
|
||||
companyFailures.forEach(failure =>
|
||||
console.log(
|
||||
`- Company ${failure.company.source.id} error: ${failure.company.error?.message}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("## User");
|
||||
console.log("- Users success:", userReports.filter(user => !user.error).length);
|
||||
console.log("- Users failure:", userFailures.length);
|
||||
if (userFailures.length) {
|
||||
console.log("### Failures");
|
||||
userFailures.forEach(failure =>
|
||||
console.log(
|
||||
`- User ${failure.user.source.user.user_id} error: ${failure.user.error?.message}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("## Owner");
|
||||
console.log("- Owners success:", ownerReports.filter(report => !report.error).length);
|
||||
console.log("- Owners failure:", ownerFailures.length);
|
||||
if (ownerFailures.length) {
|
||||
console.log("### Failures");
|
||||
ownerFailures.forEach(report =>
|
||||
console.log(`- User ${report.destinationId} error: ${report?.error?.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("## Warnings");
|
||||
console.log("### Companies witout admins");
|
||||
companiesWithoutAdmin.forEach(company => {
|
||||
console.log(`- ${company.id} - ${company.displayName}`);
|
||||
});
|
||||
|
||||
if (argv.csv) {
|
||||
reportAsCSV();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
||||
@@ -60,7 +60,7 @@ export interface ConsoleServiceClient {
|
||||
|
||||
fetchCompanyInfo(consoleCompanyCode: string): Promise<ConsoleHookCompany>;
|
||||
|
||||
getUserByAccessToken(accessToken: string): Promise<ConsoleHookUser>;
|
||||
getUserByAccessToken(idToken: string): Promise<ConsoleHookUser>;
|
||||
|
||||
resendVerificationEmail(email: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export class ConsoleInternalClient implements ConsoleServiceClient {
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
getUserByAccessToken(_accessToken: string): Promise<ConsoleHookUser> {
|
||||
getUserByAccessToken(_idToken: string): Promise<ConsoleHookUser> {
|
||||
logger.info("Internal: getUserByAccessToken");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import { v1 as uuidv1 } from "uuid";
|
||||
import { AxiosInstance } from "axios";
|
||||
import { Issuer } from "openid-client";
|
||||
import { ConsoleServiceClient } from "../client-interface";
|
||||
import {
|
||||
ConsoleCompany,
|
||||
@@ -14,16 +14,12 @@ import {
|
||||
UpdatedConsoleUserRole,
|
||||
} from "../types";
|
||||
|
||||
import User, { getInstance } from "../../user/entities/user";
|
||||
import Company, {
|
||||
CompanySearchKey,
|
||||
getInstance as getCompanyInstance,
|
||||
} from "../../user/entities/company";
|
||||
import OktaJwtVerifier from "@okta/jwt-verifier";
|
||||
import { CrudException } from "../../../core/platform/framework/api/crud-service";
|
||||
import coalesce from "../../../utils/coalesce";
|
||||
import { logger } from "../../../core/platform/framework/logger";
|
||||
import { CompanyFeaturesEnum, CompanyLimitsEnum } from "../../user/web/types";
|
||||
import gr from "../../global-resolver";
|
||||
import Company, { CompanySearchKey } from "../../user/entities/company";
|
||||
import User from "../../user/entities/user";
|
||||
import { ConsoleServiceImpl } from "../service";
|
||||
|
||||
export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
@@ -31,92 +27,30 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
client: AxiosInstance;
|
||||
|
||||
private infos: ConsoleOptions;
|
||||
private verifier: OktaJwtVerifier;
|
||||
|
||||
constructor(consoleInstance: ConsoleServiceImpl, private dryRun: boolean) {
|
||||
this.infos = consoleInstance.consoleOptions;
|
||||
this.client = axios.create({ baseURL: this.infos.url });
|
||||
this.verifier = new OktaJwtVerifier({
|
||||
issuer: this.infos.issuer,
|
||||
});
|
||||
}
|
||||
fetchCompanyInfo(consoleCompanyCode: string): Promise<ConsoleHookCompany> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
resendVerificationEmail(email: string): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
private auth() {
|
||||
return { username: this.infos.username, password: this.infos.password };
|
||||
return {};
|
||||
}
|
||||
|
||||
async addUserToCompany(
|
||||
company: ConsoleCompany,
|
||||
user: CreateConsoleUser,
|
||||
): Promise<CreatedConsoleUser> {
|
||||
logger.info("Remote: addUserToCompany");
|
||||
|
||||
const isNewConsole = this.infos.new_console;
|
||||
|
||||
if (this.dryRun) {
|
||||
return {
|
||||
_id: uuidv1(),
|
||||
};
|
||||
}
|
||||
|
||||
if (user.skipInvite && user.name && user.email && user.password) {
|
||||
return this.client
|
||||
.post(`/api/companies/${company.code}/users`, user, {
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
params: {
|
||||
skipInvite: user.skipInvite,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
} else {
|
||||
const invitationData = {
|
||||
role: user.role,
|
||||
emails: [
|
||||
{
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
},
|
||||
],
|
||||
inviter: { email: user.inviterEmail },
|
||||
...(isNewConsole
|
||||
? {
|
||||
applicationCodes: ["twake"],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const result = await this.client
|
||||
.post(
|
||||
`/api/companies/${company.code}/${isNewConsole ? "invitations" : "users/invitation"}`,
|
||||
invitationData,
|
||||
{
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(async ({ data, status }) => {
|
||||
//Fixme: When console solve https://gitlab.com/COMPANY_LINAGORA/software/saas/twake-console-account/-/issues/36
|
||||
// we can remove this fallback
|
||||
if ([200, 201].indexOf(status) >= 0) {
|
||||
return data;
|
||||
} else {
|
||||
return this.client
|
||||
.post(`/api/companies/${company.code}/users`, user, {
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
params: {
|
||||
skipInvite: user.skipInvite,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateUserRole(
|
||||
@@ -124,47 +58,12 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
user: UpdateConsoleUserRole,
|
||||
): Promise<UpdatedConsoleUserRole> {
|
||||
logger.info("Remote: updateUserRole");
|
||||
|
||||
if (this.dryRun) {
|
||||
return {
|
||||
id: user.id,
|
||||
role: user.role,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await this.client
|
||||
.put(
|
||||
`/api/companies/${company.code}/users/${user.id}`,
|
||||
{ role: user.role },
|
||||
{
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
async createCompany(company: CreateConsoleCompany): Promise<CreatedConsoleCompany> {
|
||||
logger.info("Remote: createCompany");
|
||||
|
||||
if (this.dryRun) {
|
||||
return company;
|
||||
}
|
||||
|
||||
const result = await this.client
|
||||
.post("/api/companies", company, {
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -175,216 +74,11 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
}
|
||||
|
||||
async updateLocalCompanyFromConsole(partialCompanyDTO: ConsoleHookCompany): Promise<Company> {
|
||||
logger.info("Remote: updateLocalCompanyFromConsole");
|
||||
|
||||
const companyDTO = await this.fetchCompanyInfo(partialCompanyDTO.details.code);
|
||||
|
||||
let company = await gr.services.companies.getCompany({
|
||||
identity_provider_id: companyDTO.details.code,
|
||||
});
|
||||
|
||||
if (!company) {
|
||||
const newCompany = getCompanyInstance({
|
||||
id: uuidv1(),
|
||||
identity_provider: "console",
|
||||
identity_provider_id: companyDTO.details.code,
|
||||
});
|
||||
company = await gr.services.companies.createCompany(newCompany);
|
||||
}
|
||||
|
||||
const details = companyDTO.details;
|
||||
|
||||
if (details) {
|
||||
company.name = coalesce(details.name, company.name);
|
||||
company.displayName = coalesce(details.name, company.displayName);
|
||||
|
||||
const avatar = details.avatar;
|
||||
|
||||
company.logo =
|
||||
details.logo ||
|
||||
(avatar && avatar.type && avatar.type !== "url"
|
||||
? this.infos.url.replace(/\/$/, "") + "/api/avatars/" + avatar.value
|
||||
: companyDTO.value || "");
|
||||
}
|
||||
|
||||
if (!company.plan) {
|
||||
company.plan = { name: "", limits: undefined, features: undefined };
|
||||
}
|
||||
|
||||
const limits = companyDTO.limits.twake || companyDTO.limits;
|
||||
|
||||
//FIXME this is a hack right now!
|
||||
let planFeatures: any = {
|
||||
[CompanyFeaturesEnum.CHAT_GUESTS]: true,
|
||||
[CompanyFeaturesEnum.CHAT_MESSAGE_HISTORY]: true,
|
||||
[CompanyFeaturesEnum.CHAT_MULTIPLE_WORKSPACES]: true,
|
||||
[CompanyFeaturesEnum.CHAT_EDIT_FILES]: true,
|
||||
[CompanyFeaturesEnum.CHAT_UNLIMITED_STORAGE]: true,
|
||||
[CompanyFeaturesEnum.COMPANY_INVITE_MEMBER]: true,
|
||||
};
|
||||
|
||||
if (limits.members < 0 && this.infos.type === "remote") {
|
||||
//Hack to say this is free version
|
||||
planFeatures = {
|
||||
[CompanyFeaturesEnum.CHAT_GUESTS]: false,
|
||||
[CompanyFeaturesEnum.CHAT_MESSAGE_HISTORY]: false,
|
||||
[CompanyFeaturesEnum.CHAT_MULTIPLE_WORKSPACES]: false,
|
||||
[CompanyFeaturesEnum.CHAT_EDIT_FILES]: false,
|
||||
[CompanyFeaturesEnum.CHAT_UNLIMITED_STORAGE]: false, // Currently inactive
|
||||
};
|
||||
company.plan.name = "free";
|
||||
} else {
|
||||
company.plan.name = "standard";
|
||||
}
|
||||
company.plan.features = { ...planFeatures };
|
||||
company.plan.limits = {
|
||||
[CompanyLimitsEnum.CHAT_MESSAGE_HISTORY_LIMIT]: 10000, // To remove duplicata since we define this in formatCompany function
|
||||
[CompanyLimitsEnum.COMPANY_MEMBERS_LIMIT]: limits["members"],
|
||||
[CompanyLimitsEnum.COMPANY_GUESTS_LIMIT]: limits["guests"],
|
||||
};
|
||||
|
||||
company.stats = coalesce(companyDTO.stats, company.stats);
|
||||
|
||||
await gr.services.companies.updateCompany(company);
|
||||
|
||||
return company;
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateLocalUserFromConsole(code: string): Promise<User> {
|
||||
logger.info("Remote: updateLocalUserFromConsole");
|
||||
|
||||
const userDTO = await this.fetchUserInfo(code);
|
||||
|
||||
if (!userDTO) {
|
||||
throw CrudException.badRequest("User not found on Console");
|
||||
}
|
||||
|
||||
const roles = userDTO.roles.filter(
|
||||
role => role.applications === undefined || role.applications.find(a => a.code === "twake"),
|
||||
);
|
||||
|
||||
let user = await gr.services.users.getByConsoleId(userDTO._id);
|
||||
|
||||
if (!user) {
|
||||
if (!userDTO.email) {
|
||||
throw CrudException.badRequest("Email is required");
|
||||
}
|
||||
|
||||
let username = userDTO.email
|
||||
.split("@")[0]
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[^a-zA-Z0-9]/g, "")
|
||||
.replace(/ +/g, "_");
|
||||
|
||||
if (await gr.services.users.isEmailAlreadyInUse(userDTO.email)) {
|
||||
const user = await gr.services.users.getByEmail(userDTO.email);
|
||||
|
||||
if (user.identity_provider === "console") {
|
||||
let emailUserOnConsole = null;
|
||||
try {
|
||||
emailUserOnConsole = await this.fetchUserInfo(user.identity_provider_id);
|
||||
} catch (err) {}
|
||||
if (emailUserOnConsole?._id) {
|
||||
throw CrudException.badRequest(
|
||||
`Console user not created because email already exists on console with different id: ${emailUserOnConsole._id} while requested to provision user with id: ${userDTO._id}`,
|
||||
);
|
||||
}
|
||||
|
||||
//If not present on console, then anonymise this one and create a new one
|
||||
await gr.services.users.anonymizeAndDelete(
|
||||
{ id: user.id },
|
||||
{
|
||||
user: { id: user.id, server_request: true },
|
||||
},
|
||||
);
|
||||
|
||||
//Now we can create the new user
|
||||
}
|
||||
}
|
||||
|
||||
username = await gr.services.users.getAvailableUsername(username);
|
||||
if (!username) {
|
||||
throw CrudException.badRequest("Console user not created because username already exists");
|
||||
}
|
||||
|
||||
user = getInstance({});
|
||||
user.username_canonical = (username || "").toLocaleLowerCase();
|
||||
user.email_canonical = userDTO.email;
|
||||
user.deleted = false;
|
||||
}
|
||||
|
||||
user.email_canonical = coalesce(userDTO.email, user.email_canonical);
|
||||
user.phone = "";
|
||||
user.first_name = coalesce(userDTO.name, user.first_name);
|
||||
user.last_name = coalesce(userDTO.surname, user.last_name);
|
||||
user.identity_provider = "console";
|
||||
user.identity_provider_id = userDTO._id;
|
||||
user.mail_verified = coalesce(userDTO.isVerified, user.mail_verified);
|
||||
if (userDTO.preference) {
|
||||
user.preferences = user.preferences || {};
|
||||
user.preferences.allow_tracking = coalesce(
|
||||
userDTO.preference.allowTrackingPersonalInfo,
|
||||
user.preferences?.allow_tracking,
|
||||
);
|
||||
user.preferences.language = coalesce(userDTO.preference.locale, user.preferences?.language);
|
||||
user.preferences.timezone = coalesce(userDTO.preference.timeZone, user.preferences?.timezone);
|
||||
}
|
||||
|
||||
const avatar = userDTO.avatar;
|
||||
|
||||
user.picture =
|
||||
avatar && avatar.type && avatar.type !== "url"
|
||||
? this.infos.url.replace(/\/$/, "") + "/api/avatars/" + avatar.value
|
||||
: "";
|
||||
|
||||
await gr.services.users.save(user);
|
||||
|
||||
const getCompanyByCode = async (companyCode: string) => {
|
||||
let company = await gr.services.companies.getCompany({
|
||||
identity_provider_id: companyCode,
|
||||
});
|
||||
if (!company) {
|
||||
const companyDTO = await this.fetchCompanyInfo(companyCode);
|
||||
await this.updateLocalCompanyFromConsole(companyDTO);
|
||||
company = await gr.services.companies.getCompany({
|
||||
identity_provider_id: companyCode,
|
||||
});
|
||||
}
|
||||
return company;
|
||||
};
|
||||
|
||||
const updatedListOfCompanies = [];
|
||||
if (roles) {
|
||||
for (const role of roles) {
|
||||
const companyConsoleCode = role.targetCode;
|
||||
const roleName = role.roleCode;
|
||||
const company = await getCompanyByCode(companyConsoleCode);
|
||||
if (!company) {
|
||||
throw CrudException.notFound(`Company ${companyConsoleCode} not found`);
|
||||
}
|
||||
//Make sure user is active, if not we remove it
|
||||
if (role.status !== "deactivated") {
|
||||
updatedListOfCompanies.push(company);
|
||||
const applications = (role.applications || []).map(a => a.code);
|
||||
await gr.services.companies.setUserRole(company.id, user.id, roleName, applications);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove user from companies not in the console
|
||||
const currentCompanies = await gr.services.companies.getAllForUser(user.id);
|
||||
for (const company of currentCompanies) {
|
||||
if (!updatedListOfCompanies.map(c => c.id).includes(company.group_id)) {
|
||||
await gr.services.companies.removeUserFromCompany(
|
||||
{ id: company.group_id },
|
||||
{ id: user.id },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await gr.services.users.save(user, { user: { id: user.id, server_request: true } });
|
||||
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
|
||||
async removeCompanyUser(consoleUserId: string, company: Company): Promise<void> {
|
||||
@@ -419,81 +113,25 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
await gr.services.companies.removeCompany(companySearchKey);
|
||||
}
|
||||
|
||||
fetchCompanyInfo(consoleCompanyCode: string): Promise<ConsoleHookCompany> {
|
||||
logger.info(`Remote: fetchCompanyInfo ${consoleCompanyCode}`);
|
||||
return this.client
|
||||
.get(`/api/companies/${consoleCompanyCode}`, {
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data.company)
|
||||
.catch(e => {
|
||||
if (e.response.status === 401) {
|
||||
throw CrudException.forbidden("Bad console credentials");
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
fetchUserInfo(consoleUserId: string): Promise<ConsoleHookUser> {
|
||||
logger.info(`Remote: fetchUserInfo ${consoleUserId}`);
|
||||
return this.client
|
||||
.get(`/api/users/${consoleUserId}`, {
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data)
|
||||
.catch(e => {
|
||||
if (e.response.status === 401) {
|
||||
throw CrudException.forbidden("Bad console credentials");
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
getUserByAccessToken(accessToken: string): Promise<ConsoleHookUser> {
|
||||
logger.info("Remote: getUserByAccessToken");
|
||||
return this.client
|
||||
.get("/api/users/profile", {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data)
|
||||
.catch(e => {
|
||||
if (e.response?.status === 401) {
|
||||
throw CrudException.forbidden("Bad access token credentials");
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async resendVerificationEmail(email: string) {
|
||||
logger.info("Remote: resendVerificationEmail");
|
||||
return this.client
|
||||
.post(
|
||||
"/api/users/resend-verification-email",
|
||||
{
|
||||
email,
|
||||
},
|
||||
{
|
||||
auth: this.auth(),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(({ data }) => data)
|
||||
.catch(e => {
|
||||
if (e.response.status === 401) {
|
||||
throw CrudException.forbidden("Bad credentials");
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
async getUserByAccessToken(idToken: string): Promise<ConsoleHookUser> {
|
||||
const user = (await this.verifier.verifyIdToken(idToken, this.infos.audience)).claims as any;
|
||||
console.log("user", user);
|
||||
return {
|
||||
_id: user.sub,
|
||||
roles: [] as any,
|
||||
email: user.email,
|
||||
name: user.given_name,
|
||||
surname: user.family_name,
|
||||
isVerified: true,
|
||||
preference: {
|
||||
locale: user.locale,
|
||||
timeZone: 0,
|
||||
allowTrackingPersonalInfo: true,
|
||||
},
|
||||
avatar: {
|
||||
type: "url",
|
||||
value: user.picture,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
import passwordGenerator from "generate-password";
|
||||
import { concat, EMPTY, forkJoin, from, Observable, ReplaySubject } from "rxjs";
|
||||
import {
|
||||
count,
|
||||
distinct,
|
||||
filter,
|
||||
groupBy,
|
||||
map,
|
||||
mergeMap,
|
||||
min,
|
||||
share,
|
||||
tap,
|
||||
toArray,
|
||||
} from "rxjs/operators";
|
||||
import { getLogger } from "../../../core/platform/framework";
|
||||
import {
|
||||
ExecutionContext,
|
||||
Paginable,
|
||||
Pagination,
|
||||
} from "../../../core/platform/framework/api/crud-service";
|
||||
import Company from "../../user/entities/company";
|
||||
import User from "../../user/entities/user";
|
||||
import {
|
||||
CompanyCreatedStreamObject,
|
||||
CompanyReport,
|
||||
ConsoleOptions,
|
||||
CreatedConsoleCompany,
|
||||
CreatedConsoleUser,
|
||||
MergeProgress,
|
||||
ProcessReport,
|
||||
UpdateConsoleUserRole,
|
||||
UserCreatedStreamObject,
|
||||
UserReport,
|
||||
} from "../types";
|
||||
import { getInstance as getExternalUserInstance } from "../../user/entities/external_user";
|
||||
import { getInstance as getExternalGroupInstance } from "../../user/entities/external_company";
|
||||
import CompanyUser from "../../user/entities/company_user";
|
||||
import { ConsoleRemoteClient } from "../clients/remote";
|
||||
import { ConsoleServiceClient } from "../client-interface";
|
||||
import { DatabaseServiceAPI } from "../../../core/platform/services/database/api";
|
||||
import { CompanyUserRole } from "../../user/web/types";
|
||||
import gr from "../../global-resolver";
|
||||
import { ConsoleServiceImpl } from "../service";
|
||||
|
||||
const logger = getLogger("console.process.merge");
|
||||
|
||||
export class MergeProcess {
|
||||
private client: ConsoleServiceClient;
|
||||
|
||||
constructor(
|
||||
private database: DatabaseServiceAPI,
|
||||
private dryRun: boolean,
|
||||
private consoleId: string = "console",
|
||||
private linkExternal: boolean = true,
|
||||
consoleClientOptions: ConsoleOptions,
|
||||
) {
|
||||
const consoleService = new ConsoleServiceImpl(consoleClientOptions);
|
||||
this.client = new ConsoleRemoteClient(consoleService, dryRun);
|
||||
}
|
||||
|
||||
merge(concurrent: number = 1): MergeProgress {
|
||||
const progress$ = new ReplaySubject<ProcessReport>();
|
||||
const { users$, companies$ } = this.getStreams(concurrent);
|
||||
|
||||
const owners$ = users$.pipe(
|
||||
// get only the admins
|
||||
filter(userInCompany => userInCompany.source.user.level.valueOf() === 3),
|
||||
// group by company id
|
||||
groupBy(userInCompany => userInCompany.source.company.id),
|
||||
mergeMap(group =>
|
||||
group.pipe(
|
||||
// get the admin with the smaller creation date, let's say that this is the first one in the company so it is the owner
|
||||
min((a, b) => {
|
||||
return (a.source?.user?.dateAdded || 0) <= (b.source?.user?.dateAdded || 0) ? -1 : 1;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const numberOfAdmins$ = users$.pipe(count(user => user.source.user.level.valueOf() === 3));
|
||||
const companyWithoutAdmin$ = users$.pipe(
|
||||
// group by company
|
||||
groupBy(user => user.source.company.id),
|
||||
mergeMap(group =>
|
||||
group.pipe(
|
||||
// get users as array
|
||||
// TODO: we may be able to do this without doing a toArray but just by playing with the group
|
||||
toArray(),
|
||||
// filter groups where there is no user with level === 3
|
||||
filter(users => !users.some(user => user.source.user.level.valueOf() === 3)),
|
||||
// take first element to get the company
|
||||
map(users => users[0].source.company),
|
||||
),
|
||||
),
|
||||
toArray(),
|
||||
);
|
||||
|
||||
const companiesSubscription = companies$.subscribe({
|
||||
next(company) {
|
||||
progress$.next({
|
||||
type: "company:created",
|
||||
data: {
|
||||
sourceId: company.source.id,
|
||||
destinationCode: company.destination.code,
|
||||
status: company.error ? "failure" : "success",
|
||||
company,
|
||||
error: company.error,
|
||||
} as CompanyReport,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const usersSubscription = users$.subscribe({
|
||||
next(user) {
|
||||
progress$.next({
|
||||
type: "user:created",
|
||||
data: {
|
||||
sourceId: user.source.user.id,
|
||||
destinationId: user.destination.id,
|
||||
destinationCompanyCode: user.destination.companyCode,
|
||||
status: user.error ? "failure" : "success",
|
||||
user,
|
||||
error: user.error,
|
||||
} as UserReport,
|
||||
});
|
||||
},
|
||||
error(err: Error) {
|
||||
console.error(err);
|
||||
},
|
||||
});
|
||||
|
||||
const updateOwners$ = owners$.pipe(
|
||||
mergeMap(owner => this.updateUserRole(owner, "owner"), concurrent),
|
||||
tap(user =>
|
||||
progress$.next({
|
||||
type: "user:updated",
|
||||
data: {
|
||||
sourceId: user.source.source.user.id,
|
||||
destinationId: user.source.destination.id,
|
||||
destinationCompanyCode: user.source.destination.companyCode,
|
||||
status: user.result.error ? "failure" : "success",
|
||||
user: user.source,
|
||||
error: user.result.error,
|
||||
} as UserReport,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const process$ = forkJoin([users$, companies$, numberOfAdmins$, companyWithoutAdmin$]).pipe(
|
||||
tap(result => {
|
||||
progress$.next({ type: "log", message: `There are ${result[2]} admins` });
|
||||
progress$.next({
|
||||
type: "company:withoutadmin",
|
||||
message: `Companies without admins ${result[3].length}`,
|
||||
data: result[3],
|
||||
});
|
||||
progress$.next({ type: "processing:owner" });
|
||||
}),
|
||||
mergeMap(() => from(updateOwners$), concurrent),
|
||||
);
|
||||
|
||||
const process = process$.subscribe({
|
||||
complete() {
|
||||
usersSubscription.unsubscribe();
|
||||
companiesSubscription.unsubscribe();
|
||||
process.unsubscribe();
|
||||
progress$.complete();
|
||||
},
|
||||
});
|
||||
|
||||
return progress$;
|
||||
}
|
||||
|
||||
private getStreams(
|
||||
concurrent: number = 1,
|
||||
context?: ExecutionContext,
|
||||
): {
|
||||
// hot companies observable
|
||||
companies$: Observable<CompanyCreatedStreamObject>;
|
||||
// hot users observable
|
||||
users$: Observable<UserCreatedStreamObject>;
|
||||
} {
|
||||
const companies$ = this.getCompanies().pipe(
|
||||
mergeMap(company => this.createCompany(company), concurrent),
|
||||
// make it hot
|
||||
share(),
|
||||
);
|
||||
|
||||
const users$ = companies$.pipe(
|
||||
filter(company => !company.error),
|
||||
mergeMap(company =>
|
||||
this.getUserIds(company.source).pipe(
|
||||
map(user => ({
|
||||
user,
|
||||
company,
|
||||
})),
|
||||
),
|
||||
),
|
||||
mergeMap(
|
||||
userInCompany => this.createUser(userInCompany.company.source, userInCompany.user, context),
|
||||
concurrent,
|
||||
),
|
||||
// make it hot
|
||||
share(),
|
||||
);
|
||||
|
||||
return {
|
||||
companies$,
|
||||
users$,
|
||||
};
|
||||
}
|
||||
|
||||
private getUserIds(company: Company, paginable?: Paginable): Observable<CompanyUser> {
|
||||
const pagination = new Pagination(
|
||||
paginable?.page_token,
|
||||
paginable?.limitStr,
|
||||
paginable?.reversed,
|
||||
);
|
||||
|
||||
return from(gr.services.companies.getUsers({ group_id: company.id }, pagination)).pipe(
|
||||
mergeMap(companyUsers => {
|
||||
const items$ = from(companyUsers.getEntities());
|
||||
const next$ = companyUsers?.nextPage?.page_token
|
||||
? this.getUserIds(company, companyUsers.nextPage)
|
||||
: EMPTY;
|
||||
return concat(items$, next$);
|
||||
}),
|
||||
distinct(user => user.user_id),
|
||||
);
|
||||
}
|
||||
|
||||
private getCompanies(paginable?: Paginable): Observable<Company> {
|
||||
return from(gr.services.companies.getCompanies(paginable)).pipe(
|
||||
mergeMap(companiesResult => {
|
||||
const items$ = from(companiesResult.getEntities());
|
||||
const next$ = companiesResult?.nextPage?.page_token
|
||||
? this.getCompanies(companiesResult.nextPage)
|
||||
: EMPTY;
|
||||
|
||||
return concat(items$, next$);
|
||||
}),
|
||||
distinct((company: Company) => company.id),
|
||||
);
|
||||
}
|
||||
|
||||
private async createCompany(company: Company): Promise<CompanyCreatedStreamObject> {
|
||||
logger.debug("Creating company in the Console %s", company.displayName);
|
||||
let createdCompany: CreatedConsoleCompany;
|
||||
let error;
|
||||
|
||||
try {
|
||||
createdCompany = await this.client.createCompany({
|
||||
code: company.id,
|
||||
displayName: company.displayName,
|
||||
avatar: {
|
||||
type: "url",
|
||||
value: company.logo,
|
||||
},
|
||||
status: "active",
|
||||
});
|
||||
|
||||
if (this.linkExternal) {
|
||||
await this.createCompanyLink(company, createdCompany, this.consoleId);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("Error while creating company %s", company.displayName);
|
||||
error = err;
|
||||
}
|
||||
|
||||
return {
|
||||
source: company,
|
||||
destination: {
|
||||
code: createdCompany?.code,
|
||||
},
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
private async createUser(
|
||||
company: Company,
|
||||
companyUser: CompanyUser,
|
||||
context: ExecutionContext,
|
||||
): Promise<UserCreatedStreamObject> {
|
||||
logger.debug("Creating user in console %o", companyUser.user_id);
|
||||
let error: Error;
|
||||
let result: CreatedConsoleUser;
|
||||
|
||||
try {
|
||||
const user = await gr.services.users.get({ id: companyUser.user_id });
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`User ${companyUser.user_id} not found`);
|
||||
}
|
||||
|
||||
const firstName =
|
||||
user.first_name && user.first_name.trim().length ? user.first_name : user.email_canonical;
|
||||
const lastName =
|
||||
user.last_name && user.last_name.trim().length ? user.last_name : user.email_canonical;
|
||||
const name = (firstName + " " + lastName).trim();
|
||||
|
||||
let role: "admin" | "guest" | "member" =
|
||||
companyUser.level.valueOf() === 3 ? "admin" : "member";
|
||||
if (role != "admin") {
|
||||
if (companyUser.isExterne) {
|
||||
role = "guest";
|
||||
}
|
||||
const workspacesUsers = await gr.services.workspaces.getAllForUser(
|
||||
{ userId: companyUser.user_id },
|
||||
{ id: company.id },
|
||||
);
|
||||
workspacesUsers.forEach(e => {
|
||||
if (e.isExternal) {
|
||||
role = "guest";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result = await this.client.addUserToCompany(
|
||||
{ code: company.id },
|
||||
{
|
||||
email: user.email_canonical,
|
||||
// console requires that firstname/lastname are defined and at least 1 chat long
|
||||
firstName,
|
||||
lastName,
|
||||
name,
|
||||
avatar: {
|
||||
type: "url",
|
||||
value: user.picture,
|
||||
},
|
||||
password: passwordGenerator.generate({
|
||||
length: 10,
|
||||
numbers: true,
|
||||
}),
|
||||
skipInvite: true,
|
||||
role,
|
||||
inviterEmail: "",
|
||||
},
|
||||
);
|
||||
|
||||
const companyUserRepository = await this.database.getRepository<CompanyUser>(
|
||||
"group_user",
|
||||
CompanyUser,
|
||||
);
|
||||
companyUser.role = role;
|
||||
await companyUserRepository.save(companyUser, context);
|
||||
|
||||
if (this.linkExternal) {
|
||||
await this.createUserLink(user, result, this.consoleId);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("Error while creating the user %o", companyUser.user_id);
|
||||
error = err;
|
||||
}
|
||||
|
||||
return {
|
||||
source: { user: companyUser, company },
|
||||
destination: {
|
||||
id: result?._id,
|
||||
companyCode: company.id,
|
||||
},
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
private async createUserLink(
|
||||
localUser: User,
|
||||
remoteUser: CreatedConsoleUser,
|
||||
serviceId: string,
|
||||
): Promise<void> {
|
||||
logger.debug("Creating user link for user %s", localUser.id);
|
||||
if (this.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
await gr.services.externalUser.createExternalUser(
|
||||
getExternalUserInstance({
|
||||
service_id: serviceId,
|
||||
external_id: remoteUser._id,
|
||||
user_id: localUser.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async createCompanyLink(
|
||||
localCompany: Company,
|
||||
remoteCompany: CreatedConsoleCompany,
|
||||
serviceId: string,
|
||||
): Promise<void> {
|
||||
if (this.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
await gr.services.externalUser.createExternalGroup(
|
||||
getExternalGroupInstance({
|
||||
service_id: serviceId,
|
||||
company_id: localCompany.id,
|
||||
external_id: remoteCompany.code,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private updateUserRole(
|
||||
user: UserCreatedStreamObject,
|
||||
role: CompanyUserRole,
|
||||
): Promise<{
|
||||
source: UserCreatedStreamObject;
|
||||
result: UpdateConsoleUserRole & { error?: Error };
|
||||
}> {
|
||||
logger.debug(
|
||||
`Updating user role for user ${user.source.user.id} in company ${user.source.company.id}`,
|
||||
);
|
||||
return this.client
|
||||
.updateUserRole({ code: user.destination.companyCode }, { id: user.destination.id, role })
|
||||
.then(result => ({ source: user, result }))
|
||||
.catch((err: Error) => {
|
||||
return {
|
||||
source: user,
|
||||
result: {
|
||||
id: user.destination.id,
|
||||
role,
|
||||
error: err,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DatabaseServiceAPI } from "../../core/platform/services/database/api";
|
||||
import { MergeProcess } from "./processing/merge";
|
||||
import { ConsoleOptions, ConsoleType, MergeProgress } from "./types";
|
||||
import { ConsoleServiceClient } from "./client-interface";
|
||||
import { ConsoleClientFactory } from "./client-factory";
|
||||
@@ -24,7 +23,7 @@ export class ConsoleServiceImpl implements TwakeServiceProvider {
|
||||
}
|
||||
|
||||
async init(): Promise<this> {
|
||||
this.configuration = new Configuration("console");
|
||||
this.configuration = new Configuration("general.accounts");
|
||||
assert(this.configuration, "console configuration is missing");
|
||||
const type = this.configuration.get("type") as ConsoleType;
|
||||
assert(type, "console configuration type is not defined");
|
||||
@@ -33,14 +32,12 @@ export class ConsoleServiceImpl implements TwakeServiceProvider {
|
||||
|
||||
this.consoleOptions = {
|
||||
type: type,
|
||||
new_console: s.new_console,
|
||||
username: s.username,
|
||||
password: s.password,
|
||||
url: s.url,
|
||||
hook: {
|
||||
token: s.hook?.token,
|
||||
public_key: s.hook?.public_key,
|
||||
},
|
||||
authority: s.authority,
|
||||
client_id: s.client_id,
|
||||
client_secret: s.client_secret,
|
||||
audience: s.audience,
|
||||
issuer: s.issuer,
|
||||
redirect_uris: s.redirect_uris,
|
||||
disable_account_creation: s.disable_account_creation,
|
||||
};
|
||||
|
||||
@@ -50,24 +47,6 @@ export class ConsoleServiceImpl implements TwakeServiceProvider {
|
||||
return this;
|
||||
}
|
||||
|
||||
merge(
|
||||
baseUrl: string,
|
||||
concurrent: number = 1,
|
||||
dryRun: boolean = false,
|
||||
console: string = "console",
|
||||
link: boolean = true,
|
||||
client: string,
|
||||
secret: string,
|
||||
_context?: ExecutionContext,
|
||||
): MergeProgress {
|
||||
return new MergeProcess(this.services.database, dryRun, console, link, {
|
||||
type: "remote",
|
||||
username: client,
|
||||
password: secret,
|
||||
url: baseUrl,
|
||||
} as ConsoleOptions).merge(concurrent);
|
||||
}
|
||||
|
||||
getClient(): ConsoleServiceClient {
|
||||
return ConsoleClientFactory.create(this);
|
||||
}
|
||||
|
||||
@@ -126,14 +126,12 @@ export type ConsoleType = "remote" | "internal";
|
||||
|
||||
export type ConsoleOptions = {
|
||||
type: ConsoleType;
|
||||
new_console: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
url: string;
|
||||
hook: {
|
||||
token: string;
|
||||
public_key?: string;
|
||||
};
|
||||
authority: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
issuer: string;
|
||||
audience: string;
|
||||
redirect_uris: string[];
|
||||
disable_account_creation: boolean;
|
||||
};
|
||||
|
||||
@@ -231,7 +229,7 @@ export interface ConsoleExecutionContext extends ExecutionContext {
|
||||
export interface AuthRequest {
|
||||
email?: string;
|
||||
password?: string;
|
||||
remote_access_token?: string;
|
||||
oidc_id_token?: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
|
||||
@@ -31,8 +31,8 @@ export class ConsoleController {
|
||||
}
|
||||
|
||||
async auth(request: FastifyRequest<{ Body: AuthRequest }>): Promise<AuthResponse> {
|
||||
if (request.body.remote_access_token) {
|
||||
return { access_token: await this.authByToken(request.body.remote_access_token) };
|
||||
if (request.body.oidc_id_token) {
|
||||
return { access_token: await this.authByToken(request.body.oidc_id_token) };
|
||||
} else if (request.body.email && request.body.password) {
|
||||
return { access_token: await this.authByPassword(request.body.email, request.body.password) };
|
||||
} else {
|
||||
@@ -283,12 +283,12 @@ export class ConsoleController {
|
||||
});
|
||||
}
|
||||
|
||||
private async authByToken(accessToken: string): Promise<AccessToken> {
|
||||
private async authByToken(idToken: string): Promise<AccessToken> {
|
||||
const client = gr.services.console.getClient();
|
||||
const userDTO = await client.getUserByAccessToken(accessToken);
|
||||
const userDTO = await client.getUserByAccessToken(idToken);
|
||||
const user = await client.updateLocalUserFromConsole(userDTO._id);
|
||||
if (!user) {
|
||||
throw CrudException.notFound(`User details not found for access token ${accessToken}`);
|
||||
throw CrudException.notFound(`User details not found for access token ${idToken}`);
|
||||
}
|
||||
return gr.platformServices.auth.generateJWT(user.id, user.email_canonical, {
|
||||
track: user?.preferences?.allow_tracking || false,
|
||||
|
||||
@@ -14,27 +14,7 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next)
|
||||
const accessControl = async (
|
||||
request: FastifyRequest<{ Body: ConsoleHookBody; Querystring: ConsoleHookQueryString }>,
|
||||
) => {
|
||||
if (gr.services.console.consoleType != "remote") {
|
||||
throw fastify.httpErrors.notImplemented("Hook service is only for the remote console");
|
||||
}
|
||||
|
||||
if (
|
||||
(request.body.secret_key || request.query.secret_key) !==
|
||||
gr.services.console.consoleOptions.hook.token
|
||||
) {
|
||||
throw fastify.httpErrors.forbidden("Wrong secret");
|
||||
}
|
||||
|
||||
const publicKey = gr.services.console.consoleOptions.hook.public_key;
|
||||
|
||||
if (publicKey) {
|
||||
const input = JSON.stringify({ content: request.body.content, type: request.body.type });
|
||||
const signatureBase64 = request.body.signature;
|
||||
const verifier = crypto.createVerify("RSA-SHA512").update(input);
|
||||
if (!verifier.verify(publicKey, signatureBase64, "base64")) {
|
||||
throw fastify.httpErrors.forbidden("Signature verification failed");
|
||||
}
|
||||
}
|
||||
throw fastify.httpErrors.notImplemented("Hook service doesn't exist anymore");
|
||||
};
|
||||
|
||||
fastify.route({
|
||||
|
||||
@@ -32,7 +32,7 @@ export const authenticationSchema = {
|
||||
properties: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" },
|
||||
remote_access_token: { type: "string" },
|
||||
oidc_id_token: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
|
||||
@@ -16,14 +16,15 @@ export type ServerConfiguration = {
|
||||
help_url: string | null;
|
||||
pricing_plan_url: string | null;
|
||||
app_download_url: string | null;
|
||||
app_grid: { logo: string; name: string; url: string }[];
|
||||
mobile: {
|
||||
mobile_redirect: string;
|
||||
mobile_appstore: string;
|
||||
mobile_googleplay: string;
|
||||
};
|
||||
accounts: {
|
||||
type: "console" | "internal";
|
||||
console: null | {
|
||||
type: "remote" | "internal";
|
||||
remote: null | {
|
||||
authority: string;
|
||||
client_id: string;
|
||||
account_management_url: string;
|
||||
|
||||
@@ -24,11 +24,12 @@ const routes: FastifyPluginCallback<{ configuration: ServerConfiguration["config
|
||||
"pricing_plan_url",
|
||||
"mobile",
|
||||
"app_download_url",
|
||||
"app_grid",
|
||||
),
|
||||
accounts: {
|
||||
type: accounts.type,
|
||||
console: _.pick(
|
||||
accounts.console,
|
||||
remote: _.pick(
|
||||
accounts.remote,
|
||||
"authority",
|
||||
"client_id",
|
||||
"account_management_url",
|
||||
|
||||
@@ -1456,6 +1456,14 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@okta/jwt-verifier@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@okta/jwt-verifier/-/jwt-verifier-3.0.1.tgz#10facad0424052e0cfd6477304cdc51d84ac60be"
|
||||
integrity sha512-8rNvCxAcgGlSJH9NilhYTrbZjyTPPF0ThJUDF/NI8XV/4NcqOFdO946meFsXpV1oFdZP6ASH/j2GjVhU4cHpPQ==
|
||||
dependencies:
|
||||
jwks-rsa "^3.0.0"
|
||||
njwt "^2.0.0"
|
||||
|
||||
"@redis/bloom@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.0.2.tgz#42b82ec399a92db05e29fffcdfd9235a5fc15cdf"
|
||||
@@ -1703,6 +1711,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652"
|
||||
integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
|
||||
integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/busboy@^0.2.3":
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-0.2.4.tgz#19922f8c7076ad6d47b2565da8c0a94c88776315"
|
||||
@@ -1740,6 +1756,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.36.tgz#bf53ca640f3a1a6a2072a9f33e02a44def07a40b"
|
||||
integrity sha512-EoAeT1MyFWh2BJvBDEFInY714bQBbHOAucqxqqhprhbBFqr+B7fuN5T9CJqUIGDzvwubnKKRqmSo6yPo0aSpNw==
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
|
||||
integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cookie@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
|
||||
@@ -1768,6 +1791,25 @@
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.52.tgz"
|
||||
integrity sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ==
|
||||
|
||||
"@types/express-serve-static-core@^4.17.33":
|
||||
version "4.17.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543"
|
||||
integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/qs" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@^4.17.14":
|
||||
version "4.17.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4"
|
||||
integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "^4.17.33"
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/fastify-multipart@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/fastify-multipart/-/fastify-multipart-0.7.0.tgz#fcbabfb35e69af0290a1c6c83199fc7e03855a51"
|
||||
@@ -1853,6 +1895,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/jsonwebtoken@^9.0.0":
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#29b1369c4774200d6d6f63135bf3d1ba3ef997a4"
|
||||
integrity sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/keyv@^3.1.4":
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
|
||||
@@ -1870,6 +1919,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/mime@*":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
||||
integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
|
||||
@@ -1926,6 +1980,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz"
|
||||
integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==
|
||||
|
||||
"@types/node@^15.0.1":
|
||||
version "15.14.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa"
|
||||
integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
||||
@@ -1986,11 +2045,21 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||
|
||||
"@types/random-useragent@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/random-useragent/-/random-useragent-0.3.1.tgz#1f0e1e08151f04d2c023ab77c8173dfa5b5049d2"
|
||||
integrity sha512-yVz7897JjjwS6V+lmxlu8pktzNnNWgtF9MGxfaRfI8o+hQageO9K+0hfwr8PspdFJIIRE9KkLqp4o5tlQqv/0w==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/redis@^4.0.11":
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1"
|
||||
@@ -2010,6 +2079,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d"
|
||||
integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==
|
||||
dependencies:
|
||||
"@types/mime" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/sharp@^0.29.5":
|
||||
version "0.29.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3"
|
||||
@@ -2732,7 +2809,7 @@ base64-arraybuffer@0.1.4:
|
||||
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz"
|
||||
integrity sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
@@ -3647,6 +3724,11 @@ crypto-browserify@^3.12.0:
|
||||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
crypto-js@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
|
||||
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
|
||||
|
||||
crypto-random-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
@@ -4031,7 +4113,7 @@ dynamic-dedupe@^0.3.0:
|
||||
dependencies:
|
||||
xtend "^4.0.0"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
@@ -4213,6 +4295,11 @@ es6-error@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
es6-promise@^4.2.8:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
@@ -5572,6 +5659,18 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
idtoken-verifier@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.2.3.tgz#1758e9b0596f7036134938d63e107a72045622b8"
|
||||
integrity sha512-hhpzB+MRgEvbwqzRLFdVbG55lKdXQVfeYEjAA2qu0UC72MSLeR0nX7P7rY5Dycz1aISHPOwq80hIPFoJ/+SItA==
|
||||
dependencies:
|
||||
base64-js "^1.5.1"
|
||||
crypto-js "^4.1.1"
|
||||
es6-promise "^4.2.8"
|
||||
jsbn "^1.1.0"
|
||||
unfetch "^4.2.0"
|
||||
url-join "^4.0.1"
|
||||
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@@ -6475,6 +6574,11 @@ join-component@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz"
|
||||
integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==
|
||||
|
||||
jose@^4.10.0, jose@^4.10.4:
|
||||
version "4.13.1"
|
||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.13.1.tgz#449111bb5ab171db85c03f1bd2cb1647ca06db1c"
|
||||
integrity sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==
|
||||
|
||||
joycon@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||
@@ -6500,6 +6604,11 @@ js-yaml@^4.0.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsbn@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
|
||||
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
|
||||
|
||||
jsdom@^16.4.0:
|
||||
version "16.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
||||
@@ -6651,6 +6760,18 @@ jwa@^1.4.1:
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwks-rsa@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-3.0.1.tgz#ba79ddca7ee7520f7bb26b942ef1aee91df8d7e4"
|
||||
integrity sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==
|
||||
dependencies:
|
||||
"@types/express" "^4.17.14"
|
||||
"@types/jsonwebtoken" "^9.0.0"
|
||||
debug "^4.3.4"
|
||||
jose "^4.10.4"
|
||||
limiter "^1.1.5"
|
||||
lru-memoizer "^2.1.4"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
@@ -6747,6 +6868,11 @@ light-my-request@^4.2.0:
|
||||
process-warning "^1.0.0"
|
||||
set-cookie-parser "^2.4.1"
|
||||
|
||||
limiter@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
|
||||
integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
|
||||
@@ -6776,6 +6902,11 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
|
||||
@@ -6896,6 +7027,22 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
lru-cache@~4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
|
||||
integrity sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==
|
||||
dependencies:
|
||||
pseudomap "^1.0.1"
|
||||
yallist "^2.0.0"
|
||||
|
||||
lru-memoizer@^2.1.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.2.0.tgz#b9d90c91637b4b1a423ef76f3156566691293df8"
|
||||
integrity sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==
|
||||
dependencies:
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lru-cache "~4.0.0"
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz"
|
||||
@@ -7329,6 +7476,15 @@ nice-try@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
njwt@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/njwt/-/njwt-2.0.0.tgz#da8b7ad995980de67b8069dad63949d2bd5df27d"
|
||||
integrity sha512-1RcqirhCqThBEe4KO83pFg0wPBa1c9NiXNCrocD2EbZqb6ksWWDVnp/w/p0gsyUcVa05PhhaaPjs9rc/GLmdxQ==
|
||||
dependencies:
|
||||
"@types/node" "^15.0.1"
|
||||
ecdsa-sig-formatter "^1.0.5"
|
||||
uuid "^8.3.2"
|
||||
|
||||
node-abi@^3.3.0:
|
||||
version "3.22.0"
|
||||
resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz"
|
||||
@@ -7545,6 +7701,11 @@ object-copy@^0.1.0:
|
||||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-hash@^2.0.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||
|
||||
object-inspect@^1.12.0, object-inspect@^1.9.0:
|
||||
version "1.12.2"
|
||||
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz"
|
||||
@@ -7579,6 +7740,11 @@ object.pick@^1.3.0:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
oidc-token-hash@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6"
|
||||
integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==
|
||||
|
||||
on-exit-leak-free@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4"
|
||||
@@ -7610,6 +7776,16 @@ openapi-types@^10.0.0:
|
||||
resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-10.0.0.tgz"
|
||||
integrity sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==
|
||||
|
||||
openid-client@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.4.0.tgz#77f1cda14e2911446f16ea3f455fc7c405103eac"
|
||||
integrity sha512-hgJa2aQKcM2hn3eyVtN12tEA45ECjTJPXCgUh5YzTzy9qwapCvmDTVPWOcWVL0d34zeQoQ/hbG9lJhl3AYxJlQ==
|
||||
dependencies:
|
||||
jose "^4.10.0"
|
||||
lru-cache "^6.0.0"
|
||||
object-hash "^2.0.1"
|
||||
oidc-token-hash "^5.0.1"
|
||||
|
||||
optionator@^0.8.1:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||
@@ -8077,7 +8253,7 @@ ps-tree@^1.2.0:
|
||||
dependencies:
|
||||
event-stream "=3.3.4"
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
pseudomap@^1.0.1, pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
|
||||
integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
|
||||
@@ -9695,6 +9871,11 @@ undici@^3.3.6:
|
||||
resolved "https://registry.yarnpkg.com/undici/-/undici-3.3.6.tgz#06d3b97b7eeff46bce6f8a71079c09f64dd59dc1"
|
||||
integrity sha512-/j3YTZ5AobMB4ZrTY72mzM54uFUX32v0R/JRW9G2vOyF1uSKYAx+WT8dMsAcRS13TOFISv094TxIyWYk+WEPsA==
|
||||
|
||||
unfetch@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
|
||||
integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz"
|
||||
@@ -9801,6 +9982,11 @@ urix@^0.1.0:
|
||||
resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
|
||||
integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==
|
||||
|
||||
url-join@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
|
||||
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
|
||||
|
||||
url-parse-lax@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz"
|
||||
@@ -10148,7 +10334,7 @@ yallist@4.0.0, yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yallist@^2.1.2:
|
||||
yallist@^2.0.0, yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
|
||||
|
||||
Reference in New Issue
Block a user