@@ -0,0 +1,21 @@
|
||||
import { ConsoleRemoteClient } from "./clients/remote";
|
||||
import { ConsoleServiceClient } from "./client-interface";
|
||||
import { ConsoleType } from "./types";
|
||||
import { ConsoleInternalClient } from "./clients/internal";
|
||||
import { ConsoleServiceImpl } from "./service";
|
||||
|
||||
class StaticConsoleClientFactory {
|
||||
create(consoleInstance: ConsoleServiceImpl): ConsoleServiceClient {
|
||||
const type: ConsoleType = consoleInstance.consoleType;
|
||||
switch (type) {
|
||||
case "remote":
|
||||
return new ConsoleRemoteClient(consoleInstance);
|
||||
case "internal":
|
||||
return new ConsoleInternalClient(consoleInstance);
|
||||
default:
|
||||
throw new Error(`${type} is not supported`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ConsoleClientFactory = new StaticConsoleClientFactory();
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
ConsoleCompany,
|
||||
ConsoleHookCompany,
|
||||
ConsoleHookUser,
|
||||
CreateConsoleCompany,
|
||||
CreateConsoleUser,
|
||||
CreatedConsoleCompany,
|
||||
CreatedConsoleUser,
|
||||
CreateInternalUser,
|
||||
UpdateConsoleUserRole,
|
||||
UpdatedConsoleUserRole,
|
||||
} from "./types";
|
||||
import User from "../user/entities/user";
|
||||
import Company, { CompanySearchKey } from "../user/entities/company";
|
||||
|
||||
export interface ConsoleServiceClient {
|
||||
/**
|
||||
* Create a company
|
||||
*
|
||||
* @param company
|
||||
*/
|
||||
createCompany(company: CreateConsoleCompany): Promise<CreatedConsoleCompany>;
|
||||
|
||||
/**
|
||||
* Add user to company
|
||||
*
|
||||
* @param company
|
||||
* @param user
|
||||
* @param inviter
|
||||
*/
|
||||
addUserToCompany(company: ConsoleCompany, user: CreateConsoleUser): Promise<CreatedConsoleUser>;
|
||||
|
||||
/**
|
||||
* Add user to tdrive in general (for non-console version)
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
addUserToTdrive(user: CreateInternalUser): Promise<User>;
|
||||
|
||||
/**
|
||||
* Update user role
|
||||
*
|
||||
* @param company
|
||||
* @param user
|
||||
*/
|
||||
updateUserRole(
|
||||
company: ConsoleCompany,
|
||||
user: UpdateConsoleUserRole,
|
||||
): Promise<UpdatedConsoleUserRole>;
|
||||
|
||||
updateLocalCompanyFromConsole(companyDTO: ConsoleHookCompany): Promise<Company>;
|
||||
|
||||
updateLocalUserFromConsole(user: ConsoleHookUser): Promise<User>;
|
||||
|
||||
removeCompanyUser(consoleUserId: string, company: Company): Promise<void>;
|
||||
|
||||
removeUser(consoleUserId: string): Promise<void>;
|
||||
|
||||
removeCompany(companySearchKey: CompanySearchKey): Promise<void>;
|
||||
|
||||
fetchCompanyInfo(consoleCompanyCode: string): Promise<ConsoleHookCompany>;
|
||||
|
||||
getUserByAccessToken(idToken: string): Promise<ConsoleHookUser>;
|
||||
|
||||
resendVerificationEmail(email: string): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { AxiosInstance } from "axios";
|
||||
import { merge } from "lodash";
|
||||
|
||||
import { ConsoleServiceClient } from "../client-interface";
|
||||
import {
|
||||
ConsoleCompany,
|
||||
ConsoleHookCompany,
|
||||
ConsoleHookUser,
|
||||
CreateConsoleCompany,
|
||||
CreateConsoleUser,
|
||||
CreatedConsoleCompany,
|
||||
CreatedConsoleUser,
|
||||
UpdateConsoleUserRole,
|
||||
UpdatedConsoleUserRole,
|
||||
} from "../types";
|
||||
|
||||
import { v1 as uuidv1 } from "uuid";
|
||||
import User, { getInstance as getUserInstance } from "../../user/entities/user";
|
||||
import Company, { CompanySearchKey } from "../../user/entities/company";
|
||||
import { logger } from "../../../core/platform/framework/logger";
|
||||
import gr from "../../global-resolver";
|
||||
import { ConsoleServiceImpl } from "../service";
|
||||
|
||||
export class ConsoleInternalClient implements ConsoleServiceClient {
|
||||
version: "1";
|
||||
client: AxiosInstance;
|
||||
|
||||
constructor(private consoleInstance: ConsoleServiceImpl) {}
|
||||
|
||||
async addUserToCompany(
|
||||
company: ConsoleCompany,
|
||||
user: CreateConsoleUser,
|
||||
): Promise<CreatedConsoleUser> {
|
||||
logger.info("Internal: addUserToCompany");
|
||||
await gr.services.companies.setUserRole(company.id, user.id, user.role);
|
||||
return merge(user, { _id: user.id });
|
||||
}
|
||||
|
||||
async updateUserRole(
|
||||
_company: ConsoleCompany,
|
||||
_user: UpdateConsoleUserRole,
|
||||
): Promise<UpdatedConsoleUserRole> {
|
||||
logger.info("Internal: updateUserRole");
|
||||
throw Error("ConsoleInternalClient.updateUserRole is not implemented");
|
||||
}
|
||||
|
||||
async createCompany(_company: CreateConsoleCompany): Promise<CreatedConsoleCompany> {
|
||||
logger.info("Internal: ");
|
||||
throw Error("ConsoleInternalClient.createCompany is not implemented");
|
||||
}
|
||||
|
||||
async addUserToTdrive(user: CreateConsoleUser): Promise<User> {
|
||||
logger.info("Internal: addUserToTdrive");
|
||||
const userToCreate = getUserInstance({
|
||||
id: uuidv1(),
|
||||
email_canonical: user.email,
|
||||
first_name: user.firstName,
|
||||
last_name: user.lastName,
|
||||
});
|
||||
|
||||
const createdUser = await gr.services.users.save(userToCreate).then(result => result.entity);
|
||||
if (user.password) {
|
||||
await gr.services.users.setPassword({ id: createdUser.id }, user.password);
|
||||
}
|
||||
return createdUser;
|
||||
}
|
||||
|
||||
updateLocalCompanyFromConsole(_companyDTO: ConsoleHookCompany): Promise<Company> {
|
||||
logger.info("Internal: updateLocalCompanyFromConsole");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
updateLocalUserFromConsole(_user: ConsoleHookUser): Promise<User> {
|
||||
logger.info("Internal: updateLocalUserFromConsole");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
removeCompany(_companySearchKey: CompanySearchKey): Promise<void> {
|
||||
logger.info("Internal: removeCompany");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
removeCompanyUser(_consoleUserId: string, _company: Company): Promise<void> {
|
||||
logger.info("Internal: removeCompanyUser");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
removeUser(_consoleUserId: string): Promise<void> {
|
||||
logger.info("Internal: removeUser");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
fetchCompanyInfo(_consoleCompanyCode: string): Promise<ConsoleHookCompany> {
|
||||
logger.info("Internal: fetchCompanyInfo");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
getUserByAccessToken(_idToken: string): Promise<ConsoleHookUser> {
|
||||
logger.info("Internal: getUserByAccessToken");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
|
||||
resendVerificationEmail(_accessToken: string): Promise<void> {
|
||||
logger.info("Internal: resendVerificationEmail");
|
||||
throw new Error("Method should not be implemented.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import JwksClient from "jwks-rsa";
|
||||
import nJwt from "njwt";
|
||||
|
||||
function verifyAudience(expected: string, aud: string | Array<string>) {
|
||||
if (!expected) {
|
||||
throw new Error("expected audience is required");
|
||||
}
|
||||
|
||||
if (Array.isArray(aud) && !aud.includes(expected)) {
|
||||
throw new Error(
|
||||
`audience claim ${expected} does not match one of the expected audiences: ${aud.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!Array.isArray(aud) && aud !== expected) {
|
||||
throw new Error(`audience claim ${aud} does not match expected audience: ${expected}`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyIssuer(expected: string, issuer: string) {
|
||||
if (issuer !== expected) {
|
||||
throw new Error(`issuer ${issuer} does not match expected issuer: ${expected}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class OidcJwtVerifier {
|
||||
public claimsToAssert: any;
|
||||
private issuer: string;
|
||||
private jwksUri: string;
|
||||
private jwksClient: any;
|
||||
private verifier: any;
|
||||
|
||||
constructor(options: { [key: string]: any } = {}) {
|
||||
// https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md#request-agent-options
|
||||
if (options.requestAgentOptions) {
|
||||
// jwks-rsa no longer accepts 'requestAgentOptions' and instead requires a http(s).Agent be passed directly
|
||||
const msg = `\`requestAgentOptions\` has been deprecated, use \`requestAgent\` instead.
|
||||
For more info see https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md#request-agent-options`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
this.claimsToAssert = options.assertClaims || {};
|
||||
this.issuer = options.issuer;
|
||||
this.jwksUri = options.jwksUri;
|
||||
this.jwksClient = JwksClient({
|
||||
jwksUri: this.jwksUri,
|
||||
cache: true,
|
||||
cacheMaxAge: options.cacheMaxAge || 60 * 60 * 1000,
|
||||
cacheMaxEntries: 3,
|
||||
jwksRequestsPerMinute: options.jwksRequestsPerMinute || 10,
|
||||
rateLimit: true,
|
||||
requestAgent: options.requestAgent,
|
||||
});
|
||||
this.verifier = nJwt
|
||||
.createVerifier()
|
||||
.setSigningAlgorithm("RS256")
|
||||
.withKeyResolver((kid: string, cb: any) => {
|
||||
if (kid) {
|
||||
this.jwksClient.getSigningKey(kid, (err: any, key: any) => {
|
||||
cb(err, key && (key.publicKey || key.rsaPublicKey));
|
||||
});
|
||||
} else {
|
||||
cb("No KID specified", null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
async verifyAsPromise(tokenString: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Convert to a promise
|
||||
this.verifier.verify(tokenString, (err: any, jwt: any) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const jwtBodyProxy = new Proxy(jwt.body, {});
|
||||
Object.defineProperty(jwt, "claims", {
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
value: jwtBodyProxy,
|
||||
});
|
||||
|
||||
delete jwt.body;
|
||||
resolve(jwt);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async verifyIdToken(idTokenString: string, expectedClientId: string) {
|
||||
const jwt = await this.verifyAsPromise(idTokenString);
|
||||
verifyAudience(expectedClientId, jwt.claims.aud);
|
||||
verifyIssuer(this.issuer, jwt.claims.iss);
|
||||
|
||||
return jwt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import { AxiosInstance } from "axios";
|
||||
import { ConsoleServiceClient } from "../client-interface";
|
||||
import {
|
||||
ConsoleCompany,
|
||||
ConsoleHookCompany,
|
||||
ConsoleHookUser,
|
||||
ConsoleOptions,
|
||||
CreateConsoleCompany,
|
||||
CreateConsoleUser,
|
||||
CreatedConsoleCompany,
|
||||
CreatedConsoleUser,
|
||||
UpdateConsoleUserRole,
|
||||
UpdatedConsoleUserRole,
|
||||
} from "../types";
|
||||
|
||||
import { OidcJwtVerifier } from "./remote-jwks-verifier";
|
||||
import { CrudException } from "../../../core/platform/framework/api/crud-service";
|
||||
import { logger } from "../../../core/platform/framework/logger";
|
||||
import gr from "../../global-resolver";
|
||||
import Company, { CompanySearchKey } from "../../user/entities/company";
|
||||
import User, { getInstance } from "../../user/entities/user";
|
||||
import { getInstance as getCompanyInstance } from "../../user/entities/company";
|
||||
import { ConsoleServiceImpl } from "../service";
|
||||
import coalesce from "../../../utils/coalesce";
|
||||
|
||||
export class ConsoleRemoteClient implements ConsoleServiceClient {
|
||||
version: "1";
|
||||
client: AxiosInstance;
|
||||
|
||||
private infos: ConsoleOptions;
|
||||
private verifier: OidcJwtVerifier;
|
||||
|
||||
constructor(consoleInstance: ConsoleServiceImpl) {
|
||||
this.infos = consoleInstance.consoleOptions;
|
||||
this.verifier = new OidcJwtVerifier({
|
||||
clientId: this.infos.client_id,
|
||||
issuer: this.infos.issuer?.replace(/\/+$/, ""),
|
||||
jwksUri: this.infos.jwks_uri,
|
||||
// For local deployment create a https agent that ignore self signed certificate
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
requestAgent: new (require("https").Agent)({
|
||||
rejectUnauthorized: this.infos.issuer.includes("example.com") ? false : true,
|
||||
}),
|
||||
});
|
||||
}
|
||||
fetchCompanyInfo(consoleCompanyCode: string): Promise<ConsoleHookCompany> {
|
||||
throw new Error(`Method not implemented, ${consoleCompanyCode}.`);
|
||||
}
|
||||
resendVerificationEmail(email: string): Promise<void> {
|
||||
throw new Error(`Method not implemented, ${email}.`);
|
||||
}
|
||||
|
||||
private auth() {
|
||||
return {};
|
||||
}
|
||||
|
||||
async addUserToCompany(
|
||||
company: ConsoleCompany,
|
||||
user: CreateConsoleUser,
|
||||
): Promise<CreatedConsoleUser> {
|
||||
logger.info(`Method not implemented, ${company.id}, ${user.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateUserRole(
|
||||
company: ConsoleCompany,
|
||||
user: UpdateConsoleUserRole,
|
||||
): Promise<UpdatedConsoleUserRole> {
|
||||
logger.info("Remote: updateUserRole");
|
||||
logger.info(`Method not implemented, ${company.id}, ${user.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
async createCompany(company: CreateConsoleCompany): Promise<CreatedConsoleCompany> {
|
||||
logger.info("Remote: createCompany");
|
||||
logger.info(`Method not implemented, ${company}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
addUserToTdrive(user: CreateConsoleUser): Promise<User> {
|
||||
logger.info("Remote: addUserToTdrive");
|
||||
//should do noting for real console
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
async updateLocalCompanyFromConsole(partialCompanyDTO: ConsoleHookCompany): Promise<Company> {
|
||||
logger.info(`Method not implemented, ${partialCompanyDTO}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateLocalUserFromConsole(userDTO: ConsoleHookUser): Promise<User> {
|
||||
logger.info("Remote: updateLocalUserFromConsole");
|
||||
|
||||
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 === "tdrive"),
|
||||
);
|
||||
|
||||
//REMOVE LATER
|
||||
logger.info(`Roles are: ${roles}.`);
|
||||
|
||||
let user = await gr.services.users.getByConsoleId(userDTO.email);
|
||||
|
||||
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)) {
|
||||
throw CrudException.badRequest("Console user not created because email already exists");
|
||||
}
|
||||
|
||||
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.email;
|
||||
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);
|
||||
}
|
||||
|
||||
user.picture = userDTO.avatar.value;
|
||||
|
||||
await gr.services.users.save(user);
|
||||
|
||||
//For now TDrive works with only one company as we don't get it from the SSO
|
||||
|
||||
let company = await gr.services.companies.getCompany({
|
||||
id: "00000000-0000-4000-0000-000000000000",
|
||||
});
|
||||
if (!company) {
|
||||
company = await gr.services.companies.createCompany(
|
||||
getCompanyInstance({
|
||||
id: "00000000-0000-4000-0000-000000000000",
|
||||
name: "Tdrive",
|
||||
plan: { name: "Local", limits: undefined, features: undefined },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await gr.services.companies.setUserRole(company.id, user.id, "admin");
|
||||
|
||||
await gr.services.users.save(user, { user: { id: user.id, server_request: true } });
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async removeCompanyUser(consoleUserId: string, company: Company): Promise<void> {
|
||||
logger.info("Remote: removeCompanyUser");
|
||||
|
||||
const user = await gr.services.users.getByConsoleId(consoleUserId);
|
||||
if (!user) {
|
||||
throw CrudException.notFound(`User ${consoleUserId} doesn't exists`);
|
||||
}
|
||||
await gr.services.companies.removeUserFromCompany({ id: company.id }, { id: user.id });
|
||||
}
|
||||
|
||||
async removeUser(consoleUserId: string): Promise<void> {
|
||||
logger.info("Remote: removeUser");
|
||||
|
||||
const user = await gr.services.users.getByConsoleId(consoleUserId);
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User does not exists on Tdrive.");
|
||||
}
|
||||
|
||||
await gr.services.users.anonymizeAndDelete(
|
||||
{ id: user.id },
|
||||
{
|
||||
user: { id: user.id, server_request: true },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async removeCompany(companySearchKey: CompanySearchKey): Promise<void> {
|
||||
logger.info("Remote: removeCompany");
|
||||
await gr.services.companies.removeCompany(companySearchKey);
|
||||
}
|
||||
|
||||
async getUserByAccessToken(idToken: string): Promise<ConsoleHookUser> {
|
||||
const user = (await this.verifier.verifyIdToken(idToken, this.infos.client_id))?.claims as {
|
||||
sub: string;
|
||||
email: string;
|
||||
family_name: string;
|
||||
given_name: string;
|
||||
name: string;
|
||||
locale?: string;
|
||||
picture?: string;
|
||||
};
|
||||
return {
|
||||
_id: user.email || 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Prefix, TdriveService } from "../../core/platform/framework";
|
||||
import WebServerAPI from "../../core/platform/services/webserver/provider";
|
||||
import web from "./web";
|
||||
|
||||
@Prefix("/internal/services/console/v1")
|
||||
export default class ConsoleService extends TdriveService<undefined> {
|
||||
version = "1";
|
||||
name = "console";
|
||||
|
||||
public async doInit(): Promise<this> {
|
||||
const fastify = this.context.getProvider<WebServerAPI>("webserver").getServer();
|
||||
fastify.register((instance, _opts, next) => {
|
||||
web(instance, { prefix: this.prefix });
|
||||
next();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
api(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { DatabaseServiceAPI } from "../../core/platform/services/database/api";
|
||||
import { ConsoleOptions, ConsoleType } from "./types";
|
||||
import { ConsoleServiceClient } from "./client-interface";
|
||||
import { ConsoleClientFactory } from "./client-factory";
|
||||
import User from "../user/entities/user";
|
||||
import gr from "../global-resolver";
|
||||
import { Configuration, TdriveServiceProvider } from "../../core/platform/framework";
|
||||
import assert from "assert";
|
||||
import { ExecutionContext } from "../../core/platform/framework/api/crud-service";
|
||||
|
||||
export class ConsoleServiceImpl implements TdriveServiceProvider {
|
||||
version: "1";
|
||||
|
||||
consoleType: ConsoleType;
|
||||
consoleOptions: ConsoleOptions;
|
||||
services: {
|
||||
database: DatabaseServiceAPI;
|
||||
};
|
||||
private configuration: Configuration;
|
||||
|
||||
constructor(options?: ConsoleOptions) {
|
||||
this.consoleOptions = options;
|
||||
}
|
||||
|
||||
async init(): Promise<this> {
|
||||
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");
|
||||
|
||||
const s = this.configuration.get(type) as ConsoleOptions;
|
||||
|
||||
this.consoleOptions = {
|
||||
type: type,
|
||||
authority: s.authority,
|
||||
client_id: s.client_id,
|
||||
client_secret: s.client_secret,
|
||||
audience: s.audience,
|
||||
issuer: s.issuer,
|
||||
jwks_uri: s.jwks_uri,
|
||||
redirect_uris: s.redirect_uris,
|
||||
disable_account_creation: s.disable_account_creation,
|
||||
};
|
||||
|
||||
this.consoleOptions.type = type;
|
||||
this.consoleType = type;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getClient(): ConsoleServiceClient {
|
||||
return ConsoleClientFactory.create(this);
|
||||
}
|
||||
|
||||
async processPendingUser(user: User, context?: ExecutionContext): Promise<void> {
|
||||
await gr.services.workspaces.processPendingUser(user, null, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { Observable } from "rxjs";
|
||||
import Company from "../user/entities/company";
|
||||
import CompanyUser from "../user/entities/company_user";
|
||||
import { CompanyUserRole } from "../user/web/types";
|
||||
import { ExecutionContext } from "../../core/platform/framework/api/crud-service";
|
||||
import { AccessToken } from "../../utils/types";
|
||||
|
||||
export interface CreateConsoleCompany {
|
||||
code: string;
|
||||
displayName: string;
|
||||
avatar: {
|
||||
type: "url";
|
||||
value: string;
|
||||
};
|
||||
country?: string;
|
||||
address?: string;
|
||||
logo?: string;
|
||||
status: string;
|
||||
domain?: string[];
|
||||
applications?: string[];
|
||||
planId?: string;
|
||||
limits?: {
|
||||
members?: number; //Old console version
|
||||
guests?: number; //Old console version
|
||||
tdrive: {
|
||||
members: number;
|
||||
guests: number;
|
||||
storage: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type CreatedConsoleCompany = Partial<CreateConsoleCompany>;
|
||||
|
||||
export interface CreateConsoleUser {
|
||||
id?: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
name: string;
|
||||
avatar: {
|
||||
type: "url";
|
||||
value: string;
|
||||
};
|
||||
password: string;
|
||||
role: CompanyUserRole;
|
||||
skipInvite: boolean;
|
||||
inviterEmail: string;
|
||||
}
|
||||
|
||||
export interface CreateInternalUser {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type CreatedConsoleUser = Partial<CreateConsoleUser> & { _id: string };
|
||||
|
||||
export type UpdateConsoleUserRole = Pick<CreateConsoleUser, "role"> & { id: string };
|
||||
|
||||
export type UpdatedConsoleUserRole = UpdateConsoleUserRole;
|
||||
|
||||
export type ConsoleUser = {
|
||||
id: string;
|
||||
companyCode: string;
|
||||
};
|
||||
|
||||
export type ConsoleCompany = {
|
||||
id?: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type CompanyCreatedStreamObject = {
|
||||
source: Company;
|
||||
destination: ConsoleCompany;
|
||||
// Creation error
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type UserCreatedStreamObject = {
|
||||
source: {
|
||||
user: CompanyUser;
|
||||
company: Company;
|
||||
};
|
||||
destination: ConsoleUser;
|
||||
// Creation error
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
type ReportStatus = "success" | "failure";
|
||||
|
||||
export type CompanyReport = {
|
||||
sourceId: string;
|
||||
destinationCode: string;
|
||||
status: ReportStatus;
|
||||
company: CompanyCreatedStreamObject;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type UserReport = {
|
||||
sourceId: string;
|
||||
destinationId: string;
|
||||
destinationCompanyCode: string;
|
||||
status: ReportStatus;
|
||||
user: UserCreatedStreamObject;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type MergeProgress = Observable<ProcessReport>;
|
||||
|
||||
export type ProcessReport = {
|
||||
type:
|
||||
| "user:updated"
|
||||
| "user:updating"
|
||||
| "user:created"
|
||||
| "user:error"
|
||||
| "company:created"
|
||||
| "processing:owner"
|
||||
| "log"
|
||||
| "company:withoutadmin";
|
||||
//error?: Error;
|
||||
message?: string;
|
||||
data?: UserReport | CompanyReport | Error | Company[];
|
||||
};
|
||||
|
||||
export type ConsoleType = "remote" | "internal";
|
||||
|
||||
export type ConsoleOptions = {
|
||||
type: ConsoleType;
|
||||
authority: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
issuer: string;
|
||||
jwks_uri: string;
|
||||
audience: string;
|
||||
redirect_uris: string[];
|
||||
disable_account_creation: boolean;
|
||||
};
|
||||
|
||||
export type ConsoleHookCompany = {
|
||||
stats: string;
|
||||
limits: {
|
||||
members?: number; //Old console version
|
||||
guests?: number; //Old console version
|
||||
tdrive: {
|
||||
members: number;
|
||||
guests: number;
|
||||
storage: number;
|
||||
};
|
||||
};
|
||||
value: string;
|
||||
details: {
|
||||
code: string;
|
||||
logo: string;
|
||||
avatar: {
|
||||
value: string;
|
||||
type: string;
|
||||
};
|
||||
name: string;
|
||||
country: string;
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ConsoleHookUser = {
|
||||
_id: string;
|
||||
roles: [
|
||||
{
|
||||
targetCode: string;
|
||||
roleCode: CompanyUserRole;
|
||||
status: "active" | "deactivated";
|
||||
applications: {
|
||||
code: "tdrive";
|
||||
}[];
|
||||
},
|
||||
];
|
||||
email: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
isVerified: boolean;
|
||||
preference: {
|
||||
locale: string;
|
||||
timeZone: number;
|
||||
allowTrackingPersonalInfo: boolean;
|
||||
};
|
||||
avatar: {
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ConsoleHookBodyContent = {
|
||||
company: ConsoleHookCompany;
|
||||
user: ConsoleHookUser;
|
||||
};
|
||||
|
||||
export type ConsoleHookCompanyDeletedContent = {
|
||||
companyCode: string;
|
||||
};
|
||||
|
||||
export type ConsoleHookPreferenceContent = {
|
||||
preference: {
|
||||
targetCode: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ConsoleHookBody = {
|
||||
type: string;
|
||||
content:
|
||||
| ConsoleHookBodyContent
|
||||
| ConsoleHookUser
|
||||
| ConsoleHookCompany
|
||||
| ConsoleHookCompanyDeletedContent;
|
||||
signature: string;
|
||||
secret_key?: string;
|
||||
};
|
||||
|
||||
export class ConsoleHookQueryString {
|
||||
secret_key: string;
|
||||
}
|
||||
|
||||
export class ConsoleHookResponse {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ConsoleExecutionContext extends ExecutionContext {
|
||||
options: ConsoleOptions;
|
||||
}
|
||||
|
||||
export interface AuthRequest {
|
||||
email?: string;
|
||||
password?: string;
|
||||
oidc_id_token?: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
access_token?: AccessToken;
|
||||
error?: string;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
import { FastifyReply, FastifyRequest } from "fastify";
|
||||
import {
|
||||
AuthRequest,
|
||||
AuthResponse,
|
||||
ConsoleHookBody,
|
||||
ConsoleHookBodyContent,
|
||||
ConsoleHookCompany,
|
||||
ConsoleHookCompanyDeletedContent,
|
||||
ConsoleHookPreferenceContent,
|
||||
ConsoleHookResponse,
|
||||
ConsoleHookUser,
|
||||
ConsoleType,
|
||||
} from "../types";
|
||||
import Company from "../../user/entities/company";
|
||||
import { CrudException } from "../../../core/platform/framework/api/crud-service";
|
||||
import PasswordEncoder from "../../../utils/password-encoder";
|
||||
import { AccessToken } from "../../../utils/types";
|
||||
import assert from "assert";
|
||||
import { logger } from "../../../core/platform/framework";
|
||||
import { getInstance } from "../../user/entities/user";
|
||||
import { getInstance as getCompanyInstance } from "../../../services/user/entities/company";
|
||||
import Workspace from "../../../services/workspaces/entities/workspace";
|
||||
import gr from "../../global-resolver";
|
||||
import { Configuration } from "../../../core/platform/framework";
|
||||
|
||||
export class ConsoleController {
|
||||
private passwordEncoder: PasswordEncoder;
|
||||
|
||||
constructor() {
|
||||
this.passwordEncoder = new PasswordEncoder();
|
||||
}
|
||||
|
||||
async auth(request: FastifyRequest<{ Body: AuthRequest }>): Promise<AuthResponse> {
|
||||
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 {
|
||||
throw CrudException.badRequest("remote_access_token or email+password are required");
|
||||
}
|
||||
}
|
||||
|
||||
async signup(
|
||||
request: FastifyRequest<{
|
||||
Body: { email: string; password: string; first_name: string; last_name: string };
|
||||
}>,
|
||||
): Promise<AuthResponse> {
|
||||
const configuration = new Configuration("console");
|
||||
const type = configuration.get("type") as ConsoleType;
|
||||
|
||||
try {
|
||||
//Allow only if no console is set up in this case everyone will be in the same company
|
||||
//Console is set up
|
||||
if (type !== "internal") {
|
||||
throw new Error("Unable to signup in console mode");
|
||||
}
|
||||
|
||||
//Allow only if signup isn't disabled
|
||||
if (configuration.get("disable_account_creation")) {
|
||||
throw new Error("Account creation is disabled");
|
||||
}
|
||||
|
||||
const email = request.body.email.trim().toLocaleLowerCase();
|
||||
if (await gr.services.users.getByEmail(email)) {
|
||||
throw new Error("This email is already used");
|
||||
}
|
||||
|
||||
try {
|
||||
const newUser = getInstance({
|
||||
first_name: request.body.first_name,
|
||||
last_name: request.body.last_name,
|
||||
email_canonical: email,
|
||||
username_canonical: (email.replace("@", ".") || "").toLocaleLowerCase(),
|
||||
});
|
||||
const user = await gr.services.users.create(newUser);
|
||||
await gr.services.users.setPassword({ id: user.entity.id }, request.body.password);
|
||||
|
||||
//Create a global company for all users in local mode
|
||||
const companies = await gr.services.companies.getCompanies();
|
||||
let company = companies.getEntities()?.[0];
|
||||
if (!company) {
|
||||
const newCompany = getCompanyInstance({
|
||||
name: "Tdrive",
|
||||
plan: { name: "Local", limits: undefined, features: undefined },
|
||||
});
|
||||
company = await gr.services.companies.createCompany(newCompany);
|
||||
}
|
||||
await gr.services.companies.setUserRole(company.id, user.entity.id, "admin");
|
||||
|
||||
//In case someone invited us to a workspace
|
||||
await gr.services.workspaces.processPendingUser(user.entity);
|
||||
|
||||
//If user is in no workspace, then we create one for they
|
||||
const workspaces = await gr.services.workspaces.getAllForUser(
|
||||
{ userId: user.entity.id },
|
||||
{ id: company.id },
|
||||
);
|
||||
if (workspaces.length === 0) {
|
||||
gr.services.workspaces.create(
|
||||
{
|
||||
company_id: company.id,
|
||||
name: `${
|
||||
newUser.first_name || newUser.last_name || newUser.username_canonical
|
||||
}'s space`,
|
||||
} as Workspace,
|
||||
{ user: { id: user.entity.id } },
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
access_token: await this.authByPassword(request.body.email, request.body.password),
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error("An unknown error occured");
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: err.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
async tokenRenewal(request: FastifyRequest): Promise<AuthResponse> {
|
||||
return {
|
||||
access_token: gr.platformServices.auth.generateJWT(
|
||||
request.currentUser.id,
|
||||
request.currentUser.email,
|
||||
{
|
||||
track: request.currentUser?.allow_tracking || false,
|
||||
provider_id: request.currentUser.identity_provider_id,
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async resendVerificationEmail(
|
||||
request: FastifyRequest,
|
||||
): Promise<{ success: boolean; email: string }> {
|
||||
const user = await gr.services.users.get({ id: request.currentUser.id });
|
||||
|
||||
await gr.services.console.getClient().resendVerificationEmail(user.email_canonical);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
email: user.email_canonical,
|
||||
};
|
||||
}
|
||||
|
||||
private async getCompanyDataFromConsole(
|
||||
company: ConsoleHookCompany | ConsoleHookCompany["details"] | { code: string },
|
||||
): Promise<ConsoleHookCompany> {
|
||||
return gr.services.console
|
||||
.getClient()
|
||||
.fetchCompanyInfo(
|
||||
(company as ConsoleHookCompany["details"])?.code ||
|
||||
(company as ConsoleHookCompany)?.details?.code,
|
||||
);
|
||||
}
|
||||
|
||||
private async updateCompany(company: ConsoleHookCompany): Promise<Company> {
|
||||
const companyDTO = await this.getCompanyDataFromConsole(company);
|
||||
return gr.services.console.getClient().updateLocalCompanyFromConsole(companyDTO);
|
||||
}
|
||||
|
||||
async hook(
|
||||
request: FastifyRequest<{ Body: ConsoleHookBody }>,
|
||||
reply: FastifyReply,
|
||||
): Promise<ConsoleHookResponse> {
|
||||
try {
|
||||
logger.info(`Received event ${request.body.type}`);
|
||||
|
||||
switch (request.body.type) {
|
||||
case "user_created":
|
||||
case "company_user_added":
|
||||
case "company_user_activated":
|
||||
case "company_user_updated":
|
||||
await this.userAdded(request.body.content as ConsoleHookBodyContent);
|
||||
break;
|
||||
case "company_user_deactivated":
|
||||
case "company_user_deleted":
|
||||
await this.userDisabled(request.body.content as ConsoleHookBodyContent);
|
||||
break;
|
||||
case "user_updated":
|
||||
await this.userUpdated((request.body.content as ConsoleHookBodyContent).user._id);
|
||||
break;
|
||||
case "user_preference_updated":
|
||||
await this.userUpdated(
|
||||
(request.body.content as unknown as ConsoleHookPreferenceContent).preference.targetCode,
|
||||
);
|
||||
break;
|
||||
case "user_deleted":
|
||||
await this.userRemoved(request.body.content as ConsoleHookUser);
|
||||
break;
|
||||
case "plan_updated":
|
||||
await this.planUpdated(request.body.content as ConsoleHookBodyContent);
|
||||
break;
|
||||
case "company_deleted":
|
||||
await this.companyRemoved(request.body.content as ConsoleHookCompanyDeletedContent);
|
||||
break;
|
||||
case "company_created":
|
||||
await this.companyUpdated(request.body.content as ConsoleHookBodyContent);
|
||||
if ((request.body.content as ConsoleHookBodyContent)?.user?._id) {
|
||||
await this.userUpdated((request.body.content as ConsoleHookBodyContent).user._id);
|
||||
}
|
||||
break;
|
||||
case "company_updated":
|
||||
await this.companyUpdated(request.body.content as ConsoleHookBodyContent);
|
||||
break;
|
||||
default:
|
||||
logger.info("Event not recognized");
|
||||
throw CrudException.notImplemented("Unimplemented");
|
||||
}
|
||||
} catch (e) {
|
||||
reply.status(400);
|
||||
return {
|
||||
error: e.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
private async userAdded(content: ConsoleHookBodyContent): Promise<void> {
|
||||
const userDTO = content.user;
|
||||
const user = await gr.services.console.getClient().updateLocalUserFromConsole(userDTO);
|
||||
await this.updateCompany(content.company);
|
||||
await gr.services.console.processPendingUser(user);
|
||||
}
|
||||
|
||||
private async userDisabled(content: ConsoleHookBodyContent): Promise<void> {
|
||||
const company = await this.updateCompany(content.company);
|
||||
await gr.services.console.getClient().removeCompanyUser(content.user._id, company);
|
||||
}
|
||||
|
||||
private async userRemoved(content: ConsoleHookUser): Promise<void> {
|
||||
await gr.services.console.getClient().removeUser(content._id);
|
||||
}
|
||||
|
||||
private async userUpdated(code: string) {
|
||||
//Not implemented yet
|
||||
throw CrudException.notImplemented(`Method not implemented, ${code}.`);
|
||||
}
|
||||
|
||||
private async companyRemoved(content: ConsoleHookCompanyDeletedContent) {
|
||||
assert(content.companyCode, "content.companyCode is missing");
|
||||
|
||||
await gr.services.console.getClient().removeCompany({
|
||||
identity_provider: "console",
|
||||
identity_provider_id: content.companyCode,
|
||||
});
|
||||
}
|
||||
|
||||
private async companyUpdated(content: ConsoleHookBodyContent) {
|
||||
await this.updateCompany(content.company);
|
||||
}
|
||||
|
||||
private async planUpdated(content: ConsoleHookBodyContent) {
|
||||
await this.updateCompany(content.company);
|
||||
}
|
||||
|
||||
private async authByPassword(email: string, password: string): Promise<AccessToken> {
|
||||
const user = await gr.services.users.getByEmail(email);
|
||||
if (!user) {
|
||||
throw CrudException.forbidden("User doesn't exists");
|
||||
}
|
||||
|
||||
// allow to login in development mode with any password. This can be used to test without the console provider because the password is not stored locally...
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
const [storedPassword, salt] = await gr.services.users.getHashedPassword({
|
||||
id: user.id,
|
||||
});
|
||||
|
||||
if (!(await this.passwordEncoder.isPasswordValid(storedPassword, password, salt))) {
|
||||
throw CrudException.forbidden("Password doesn't match");
|
||||
}
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
logger.warn("ERROR_NOTONPROD: YOU ARE RUNNING IN DEVELOPMENT MODE, AUTH IS DISABLED!!!");
|
||||
}
|
||||
|
||||
return gr.platformServices.auth.generateJWT(user.id, user.email_canonical, {
|
||||
track: user?.preferences?.allow_tracking || false,
|
||||
provider_id: user.identity_provider_id,
|
||||
});
|
||||
}
|
||||
|
||||
private async authByToken(idToken: string): Promise<AccessToken> {
|
||||
const client = gr.services.console.getClient();
|
||||
const userDTO = await client.getUserByAccessToken(idToken);
|
||||
const user = await client.updateLocalUserFromConsole(userDTO);
|
||||
if (!user) {
|
||||
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,
|
||||
provider_id: user.identity_provider_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FastifyInstance, FastifyRegisterOptions } from "fastify";
|
||||
import routes from "./routes";
|
||||
|
||||
export default (
|
||||
fastify: FastifyInstance,
|
||||
options: FastifyRegisterOptions<{
|
||||
prefix: string;
|
||||
}>,
|
||||
): void => {
|
||||
fastify.log.debug("Configuring /console routes");
|
||||
fastify.register(routes, options);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { FastifyInstance, FastifyPluginCallback, FastifyRequest } from "fastify";
|
||||
import { authenticationSchema, consoleHookSchema, tokenRenewalSchema } from "./schemas";
|
||||
// import { WorkspaceBaseRequest, WorkspaceUsersBaseRequest, WorkspaceUsersRequest } from "./types";
|
||||
import { ConsoleController } from "./controller";
|
||||
import { ConsoleHookBody, ConsoleHookQueryString } from "../types";
|
||||
|
||||
const hookUrl = "/hook";
|
||||
|
||||
const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) => {
|
||||
const controller = new ConsoleController();
|
||||
|
||||
const accessControl = async (
|
||||
request: FastifyRequest<{ Body: ConsoleHookBody; Querystring: ConsoleHookQueryString }>,
|
||||
) => {
|
||||
throw fastify.httpErrors.notImplemented(`Hook service doesn't exist anymore, ${request}.`);
|
||||
};
|
||||
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: `${hookUrl}`,
|
||||
preHandler: [accessControl],
|
||||
schema: consoleHookSchema,
|
||||
handler: controller.hook.bind(controller),
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/login",
|
||||
schema: authenticationSchema,
|
||||
handler: controller.auth.bind(controller),
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/signup",
|
||||
handler: controller.signup.bind(controller),
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/token",
|
||||
preValidation: [fastify.authenticate],
|
||||
schema: tokenRenewalSchema,
|
||||
handler: controller.tokenRenewal.bind(controller),
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/resend-verification-email",
|
||||
preValidation: [fastify.authenticate],
|
||||
handler: controller.resendVerificationEmail.bind(controller),
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,77 @@
|
||||
export const consoleHookSchema = {
|
||||
querystring: {
|
||||
type: "object",
|
||||
properties: {
|
||||
secret_key: { type: "string" },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
content: { type: "object" },
|
||||
signature: { type: "string" },
|
||||
secret_key: { type: "string" },
|
||||
},
|
||||
required: ["type", "content"],
|
||||
},
|
||||
response: {
|
||||
"2xx": {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: { type: "boolean" },
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const authenticationSchema = {
|
||||
body: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string" },
|
||||
password: { type: "string" },
|
||||
oidc_id_token: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
"2xx": {
|
||||
type: "object",
|
||||
properties: {
|
||||
access_token: {
|
||||
type: "object",
|
||||
properties: {
|
||||
time: { type: "number" },
|
||||
expiration: { type: "number" },
|
||||
refresh_expiration: { type: "number" },
|
||||
value: { type: "string" },
|
||||
refresh: { type: "string" },
|
||||
type: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const tokenRenewalSchema = {
|
||||
response: {
|
||||
"2xx": {
|
||||
type: "object",
|
||||
properties: {
|
||||
access_token: {
|
||||
type: "object",
|
||||
properties: {
|
||||
time: { type: "number" },
|
||||
expiration: { type: "number" },
|
||||
refresh_expiration: { type: "number" },
|
||||
value: { type: "string" },
|
||||
refresh: { type: "string" },
|
||||
type: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user