musarehmani100's picture
Upload folder using huggingface_hub
3206347 verified
import bcrypt from 'bcrypt';
import { DateTime } from 'luxon';
import crypto from 'node:crypto';
import appConfig from '../config/app.js';
import { hasValidLicense } from '../helpers/license.ee.js';
import userAbility from '../helpers/user-ability.js';
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
import Base from './base.js';
import App from './app.js';
import AccessToken from './access-token.js';
import Connection from './connection.js';
import Config from './config.js';
import Execution from './execution.js';
import Flow from './flow.js';
import Identity from './identity.ee.js';
import Permission from './permission.js';
import Role from './role.js';
import Step from './step.js';
import Subscription from './subscription.ee.js';
import UsageData from './usage-data.ee.js';
import Billing from '../helpers/billing/index.ee.js';
class User extends Base {
static tableName = 'users';
static jsonSchema = {
type: 'object',
required: ['fullName', 'email'],
properties: {
id: { type: 'string', format: 'uuid' },
fullName: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 },
password: { type: 'string' },
resetPasswordToken: { type: 'string' },
resetPasswordTokenSentAt: { type: 'string' },
trialExpiryDate: { type: 'string' },
roleId: { type: 'string', format: 'uuid' },
deletedAt: { type: 'string' },
createdAt: { type: 'string' },
updatedAt: { type: 'string' },
},
};
static relationMappings = () => ({
accessTokens: {
relation: Base.HasManyRelation,
modelClass: AccessToken,
join: {
from: 'users.id',
to: 'access_tokens.user_id',
},
},
connections: {
relation: Base.HasManyRelation,
modelClass: Connection,
join: {
from: 'users.id',
to: 'connections.user_id',
},
},
flows: {
relation: Base.HasManyRelation,
modelClass: Flow,
join: {
from: 'users.id',
to: 'flows.user_id',
},
},
steps: {
relation: Base.ManyToManyRelation,
modelClass: Step,
join: {
from: 'users.id',
through: {
from: 'flows.user_id',
to: 'flows.id',
},
to: 'steps.flow_id',
},
},
executions: {
relation: Base.ManyToManyRelation,
modelClass: Execution,
join: {
from: 'users.id',
through: {
from: 'flows.user_id',
to: 'flows.id',
},
to: 'executions.flow_id',
},
},
usageData: {
relation: Base.HasManyRelation,
modelClass: UsageData,
join: {
from: 'usage_data.user_id',
to: 'users.id',
},
},
currentUsageData: {
relation: Base.HasOneRelation,
modelClass: UsageData,
join: {
from: 'usage_data.user_id',
to: 'users.id',
},
filter(builder) {
builder.orderBy('created_at', 'desc').limit(1).first();
},
},
subscriptions: {
relation: Base.HasManyRelation,
modelClass: Subscription,
join: {
from: 'subscriptions.user_id',
to: 'users.id',
},
},
currentSubscription: {
relation: Base.HasOneRelation,
modelClass: Subscription,
join: {
from: 'subscriptions.user_id',
to: 'users.id',
},
filter(builder) {
builder.orderBy('created_at', 'desc').limit(1).first();
},
},
role: {
relation: Base.HasOneRelation,
modelClass: Role,
join: {
from: 'roles.id',
to: 'users.role_id',
},
},
permissions: {
relation: Base.HasManyRelation,
modelClass: Permission,
join: {
from: 'users.role_id',
to: 'permissions.role_id',
},
},
identities: {
relation: Base.HasManyRelation,
modelClass: Identity,
join: {
from: 'identities.user_id',
to: 'users.id',
},
},
});
get authorizedFlows() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
}
get authorizedSteps() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('steps') : Step.query();
}
get authorizedConnections() {
const conditions = this.can('read', 'Connection');
return conditions.isCreator
? this.$relatedQuery('connections')
: Connection.query();
}
get authorizedExecutions() {
const conditions = this.can('read', 'Execution');
return conditions.isCreator
? this.$relatedQuery('executions')
: Execution.query();
}
static async authenticate(email, password) {
const user = await User.query().findOne({
email: email?.toLowerCase() || null,
});
if (user && (await user.login(password))) {
const token = await createAuthTokenByUserId(user.id);
return token;
}
}
login(password) {
return bcrypt.compare(password, this.password);
}
async generateResetPasswordToken() {
const resetPasswordToken = crypto.randomBytes(64).toString('hex');
const resetPasswordTokenSentAt = new Date().toISOString();
await this.$query().patch({ resetPasswordToken, resetPasswordTokenSentAt });
}
async resetPassword(password) {
return await this.$query().patch({
resetPasswordToken: null,
resetPasswordTokenSentAt: null,
password,
});
}
async isResetPasswordTokenValid() {
if (!this.resetPasswordTokenSentAt) {
return false;
}
const sentAt = new Date(this.resetPasswordTokenSentAt);
const now = new Date();
const fourHoursInMilliseconds = 1000 * 60 * 60 * 4;
return now.getTime() - sentAt.getTime() < fourHoursInMilliseconds;
}
async generateHash() {
if (this.password) {
this.password = await bcrypt.hash(this.password, 10);
}
}
async startTrialPeriod() {
this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
}
async isAllowedToRunFlows() {
if (appConfig.isSelfHosted) {
return true;
}
if (await this.inTrial()) {
return true;
}
if ((await this.hasActiveSubscription()) && (await this.withinLimits())) {
return true;
}
return false;
}
async inTrial() {
if (appConfig.isSelfHosted) {
return false;
}
if (!this.trialExpiryDate) {
return false;
}
if (await this.hasActiveSubscription()) {
return false;
}
const expiryDate = DateTime.fromJSDate(this.trialExpiryDate);
const now = DateTime.now();
return now < expiryDate;
}
async hasActiveSubscription() {
if (!appConfig.isCloud) {
return false;
}
const subscription = await this.$relatedQuery('currentSubscription');
return subscription?.isValid;
}
async withinLimits() {
const currentSubscription = await this.$relatedQuery('currentSubscription');
const plan = currentSubscription.plan;
const currentUsageData = await this.$relatedQuery('currentUsageData');
return currentUsageData.consumedTaskCount < plan.quota;
}
async getPlanAndUsage() {
const usageData = await this.$relatedQuery(
'currentUsageData'
).throwIfNotFound();
const subscription = await this.$relatedQuery('currentSubscription');
const currentPlan = Billing.paddlePlans.find(
(plan) => plan.productId === subscription?.paddlePlanId
);
const planAndUsage = {
usage: {
task: usageData.consumedTaskCount,
},
plan: {
id: subscription?.paddlePlanId || null,
name: subscription ? currentPlan.name : 'Free Trial',
limit: currentPlan?.limit || null,
},
};
return planAndUsage;
}
async getInvoices() {
const subscription = await this.$relatedQuery('currentSubscription');
if (!subscription) {
return [];
}
const invoices = await Billing.paddleClient.getInvoices(
Number(subscription.paddleSubscriptionId)
);
return invoices;
}
async getApps(name) {
const connections = await this.authorizedConnections
.clone()
.select('connections.key')
.where({ draft: false })
.count('connections.id as count')
.groupBy('connections.key');
const flows = await this.authorizedFlows
.clone()
.withGraphJoined('steps')
.orderBy('created_at', 'desc');
const duplicatedUsedApps = flows
.map((flow) => flow.steps.map((step) => step.appKey))
.flat()
.filter(Boolean);
const connectionKeys = connections.map((connection) => connection.key);
const usedApps = [...new Set([...duplicatedUsedApps, ...connectionKeys])];
let apps = await App.findAll(name);
apps = apps
.filter((app) => {
return usedApps.includes(app.key);
})
.map((app) => {
const connection = connections.find(
(connection) => connection.key === app.key
);
app.connectionCount = connection?.count || 0;
app.flowCount = 0;
flows.forEach((flow) => {
const usedFlow = flow.steps.find((step) => step.appKey === app.key);
if (usedFlow) {
app.flowCount += 1;
}
});
return app;
})
.sort((appA, appB) => appA.name.localeCompare(appB.name));
return apps;
}
static async createAdmin({ email, password, fullName }) {
const adminRole = await Role.findAdmin();
const adminUser = await this.query().insert({
email,
password,
fullName,
roleId: adminRole.id
});
await Config.markInstallationCompleted();
return adminUser;
}
async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext);
this.email = this.email.toLowerCase();
await this.generateHash();
if (appConfig.isCloud) {
await this.startTrialPeriod();
}
}
async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext);
if (this.email) {
this.email = this.email.toLowerCase();
}
await this.generateHash();
}
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
if (appConfig.isCloud) {
await this.$relatedQuery('usageData').insert({
userId: this.id,
consumedTaskCount: 0,
nextResetAt: DateTime.now().plus({ days: 30 }).toISODate(),
});
}
}
async $afterFind() {
if (await hasValidLicense()) return this;
if (Array.isArray(this.permissions)) {
this.permissions = this.permissions.filter((permission) => {
const restrictedSubjects = [
'App',
'Role',
'SamlAuthProvider',
'Config',
];
return !restrictedSubjects.includes(permission.subject);
});
}
return this;
}
get ability() {
return userAbility(this);
}
can(action, subject) {
const can = this.ability.can(action, subject);
if (!can) throw new Error('Not authorized!');
const relevantRule = this.ability.relevantRuleFor(action, subject);
const conditions = relevantRule?.conditions || [];
const conditionMap = Object.fromEntries(
conditions.map((condition) => [condition, true])
);
return conditionMap;
}
cannot(action, subject) {
const cannot = this.ability.cannot(action, subject);
if (cannot) throw new Error('Not authorized!');
return cannot;
}
}
export default User;