📁 Changed TDrive root folder (#16)

📁 Changed TDrive root folder
This commit is contained in:
Montassar Ghanmy
2023-04-11 10:04:29 +01:00
committed by GitHub
parent 3b3f67af07
commit e0615fa867
10548 changed files with 48 additions and 48 deletions
@@ -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" },
},
},
},
},
},
};