Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { promisify } from 'util';
2import bcrypt from 'bcryptjs';
3import sequelize, {
4 type CreationOptional,
5 type HasManyAddAssociationsMixin,
6 type HasManyGetAssociationsMixin,
7 type HasManyRemoveAssociationsMixin,
8 type InferAttributes,
9 type InferCreationAttributes,
10 type Sequelize,
11} from 'sequelize';
12
13import { type DataTypes } from './index.js';
14import { getPermissionsForRole, UserRole } from './types/permissioning.js';
15
16const { Model } = sequelize;
17const bcryptCompare = promisify(bcrypt.compare);
18
19export type User = InstanceType<ReturnType<typeof makeUserModel>>;
20
21/**
22 * Data Model for Users. Users are Coop users who have
23 * created profiles on our website. Actors (see ActorModel.js)
24 * are users on the organization's platforms that upload potentially
25 * problematic content.
26 */
27const makeUserModel = (sequelize: Sequelize, DataTypes: DataTypes) => {
28 class User extends Model<
29 InferAttributes<User, { omit: 'createdAt' | 'updatedAt' }>,
30 InferCreationAttributes<User, { omit: 'createdAt' | 'updatedAt' }>
31 > {
32 public declare id: string;
33 public declare email: string;
34 public declare password: string | null;
35 public declare firstName: string;
36 public declare lastName: string;
37 public declare orgId: string;
38 public declare role: CreationOptional<UserRole>;
39 public declare approvedByAdmin: CreationOptional<boolean>;
40 public declare rejectedByAdmin: CreationOptional<boolean>;
41 public declare createdAt: Date;
42 public declare updatedAt: Date;
43 public declare loginMethods: ('password' | 'saml')[];
44
45 // Have to use any below to avoid circular type errors
46 /* eslint-disable @typescript-eslint/no-explicit-any */
47 public declare addFavoriteRules: HasManyAddAssociationsMixin<any, string>;
48 public declare removeFavoriteRules: HasManyRemoveAssociationsMixin<
49 any,
50 string
51 >;
52 public declare getFavoriteRules: HasManyGetAssociationsMixin<any>;
53 /* eslint-enable @typescript-eslint/no-explicit-any */
54
55 // eslint-disable-next-line @typescript-eslint/no-explicit-any
56 static associate(models: { [key: string]: any }) {
57 User.belongsTo(models.Org, { as: 'Org' });
58 User.hasMany(models.Rule, { as: 'Rules', foreignKey: 'creatorId' });
59 User.hasMany(models.LocationBank, {
60 as: 'LocationBanks',
61 foreignKey: 'ownerId',
62 });
63 User.hasMany(models.Backtest, {
64 as: 'Backtests',
65 foreignKey: 'creatorId',
66 });
67 User.belongsToMany(models.Rule, {
68 as: 'FavoriteRules',
69 through: 'users_and_favorite_rules',
70 });
71 }
72
73 static async passwordMatchesHash(givenPassword: string, hash: string) {
74 return bcryptCompare(givenPassword, hash);
75 }
76
77 public getPermissions() {
78 return getPermissionsForRole(this.role);
79 }
80 }
81
82 /* Fields */
83 User.init(
84 {
85 id: {
86 type: DataTypes.STRING,
87 primaryKey: true,
88 },
89 orgId: {
90 type: DataTypes.STRING,
91 allowNull: false,
92 },
93 email: {
94 type: DataTypes.STRING,
95 unique: true,
96 allowNull: false,
97 validate: {
98 isEmail: true,
99 notEmpty: true,
100 },
101 },
102 password: {
103 type: DataTypes.STRING,
104 unique: false,
105 },
106 firstName: {
107 type: DataTypes.STRING,
108 unique: false,
109 allowNull: false,
110 validate: {
111 notEmpty: true,
112 },
113 },
114 lastName: {
115 type: DataTypes.STRING,
116 unique: false,
117 allowNull: false,
118 validate: {
119 notEmpty: true,
120 },
121 },
122 role: {
123 type: DataTypes.STRING,
124 unique: false,
125 defaultValue: UserRole.ADMIN,
126 validate: {
127 isIn: [Object.values(UserRole)],
128 },
129 },
130 // Has the user been approved by the admin as part of the org
131 approvedByAdmin: {
132 type: DataTypes.BOOLEAN,
133 defaultValue: false,
134 },
135 // Has the user been rejected by the admin as part of the org
136 rejectedByAdmin: {
137 type: DataTypes.BOOLEAN,
138 defaultValue: false,
139 },
140 loginMethods: {
141 type: DataTypes.ARRAY(DataTypes.ENUM('password', 'saml')),
142 defaultValue: ['password'],
143 allowNull: false,
144 },
145 },
146 {
147 sequelize,
148 modelName: 'user',
149 underscored: true,
150 },
151 );
152
153 return User;
154};
155
156export default makeUserModel;