crazy-odds-bet-shared 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/models/index.js CHANGED
@@ -6,6 +6,8 @@ const { Team } = require('./team');
6
6
  const { Player } = require('./player');
7
7
  const { Fixture, FixtureStatus } = require('./fixture');
8
8
  const { User } = require('./user');
9
+ const { Permission } = require('./permission');
10
+ const { Role } = require('./role');
9
11
  const { MarketTemplate } = require('./marketTemplate');
10
12
  const { Market } = require('./market');
11
13
  const { Odd } = require('./odd');
@@ -23,6 +25,8 @@ module.exports = {
23
25
  Fixture,
24
26
  FixtureStatus,
25
27
  User,
28
+ Permission,
29
+ Role,
26
30
  MarketTemplate,
27
31
  Market,
28
32
  Odd,
@@ -0,0 +1,21 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * Permission schema - granular permission codes (e.g. "users:read", "roles:write")
6
+ */
7
+ const permissionSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+ code: { type: String, required: true, unique: true, trim: true },
11
+ name: { type: String, required: true, trim: true },
12
+ description: { type: String, default: '' }
13
+ },
14
+ baseSchemaOptions
15
+ );
16
+
17
+ permissionSchema.index({ code: 1 });
18
+
19
+ const Permission = model('Permission', permissionSchema);
20
+
21
+ module.exports = { Permission };
package/models/role.js ADDED
@@ -0,0 +1,21 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * Role schema - named roles with a set of permission IDs (inherited by users assigned this role)
6
+ */
7
+ const roleSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+ name: { type: String, required: true, unique: true, trim: true },
11
+ description: { type: String, default: '' },
12
+ permissionIds: [{ type: String, ref: 'Permission' }]
13
+ },
14
+ baseSchemaOptions
15
+ );
16
+
17
+ roleSchema.index({ name: 1 });
18
+
19
+ const Role = model('Role', roleSchema);
20
+
21
+ module.exports = { Role };
package/models/user.js CHANGED
@@ -1,6 +1,10 @@
1
+ const bcrypt = require('bcrypt');
1
2
  const { Schema, model } = require('mongoose');
2
3
  const { createBaseSchema, baseSchemaOptions } = require('./base');
3
4
 
5
+ const BCRYPT_ROUNDS = 10;
6
+ const BCRYPT_PREFIX = '$2';
7
+
4
8
  /**
5
9
  * User schema
6
10
  */
@@ -10,15 +14,32 @@ const userSchema = new Schema(
10
14
  email: { type: String, required: true, unique: true, lowercase: true, trim: true },
11
15
  password: { type: String, required: true },
12
16
  name: { type: String, required: true },
13
- role: { type: String, enum: ['admin', 'user'], default: 'user' },
17
+ roleId: { type: String, ref: 'Role', default: null },
18
+ /** Custom permission IDs added on top of role permissions */
19
+ permissionIds: [{ type: String, ref: 'Permission' }],
20
+ config: { type: Schema.Types.Mixed, default: {} },
14
21
  isActive: { type: Boolean, default: true }
15
22
  },
16
23
  baseSchemaOptions
17
24
  );
18
25
 
26
+ // Hash password before saving (only when password was modified and not already a hash)
27
+ userSchema.pre('save', async function (next) {
28
+ if (!this.isModified('password')) return next();
29
+ if (typeof this.password !== 'string') return next();
30
+ if (this.password.startsWith(BCRYPT_PREFIX)) return next();
31
+ try {
32
+ this.password = await bcrypt.hash(this.password, BCRYPT_ROUNDS);
33
+ next();
34
+ } catch (err) {
35
+ next(err);
36
+ }
37
+ });
38
+
19
39
  // Indexes
20
40
  userSchema.index({ isActive: 1 });
21
- userSchema.index({ role: 1 });
41
+ userSchema.index({ roleId: 1 });
42
+ userSchema.index({ email: 1, isActive: 1 });
22
43
 
23
44
  // Static methods
24
45
  userSchema.static('findByEmail', function (email) {
@@ -29,8 +50,11 @@ userSchema.static('getActive', function () {
29
50
  return this.find({ isActive: true }).select('-password');
30
51
  });
31
52
 
32
- userSchema.static('findAdmins', function () {
33
- return this.find({ role: 'admin', isActive: true }).select('-password');
53
+ userSchema.static('findAdmins', async function () {
54
+ const Role = this.db.model('Role');
55
+ const adminRole = await Role.findOne({ name: 'Admin' }).lean();
56
+ if (!adminRole) return this.find({ _id: { $in: [] } }).select('-password');
57
+ return this.find({ roleId: adminRole._id, isActive: true }).select('-password');
34
58
  });
35
59
 
36
60
  // Instance methods
@@ -45,8 +69,18 @@ userSchema.method('deactivate', async function () {
45
69
  });
46
70
 
47
71
  userSchema.method('comparePassword', async function (password) {
48
- // For now, simple comparison. In production, use bcrypt
49
- return this.password === password;
72
+ if (!this.password) return false;
73
+ if (this.password.startsWith(BCRYPT_PREFIX)) {
74
+ const match = await bcrypt.compare(password, this.password);
75
+ if (match) return true;
76
+ return false;
77
+ }
78
+ const plainMatch = this.password === password;
79
+ if (plainMatch) {
80
+ this.password = await bcrypt.hash(password, BCRYPT_ROUNDS);
81
+ await this.save({ validateBeforeSave: false });
82
+ }
83
+ return plainMatch;
50
84
  });
51
85
 
52
86
  // Middleware to exclude password from toJSON
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crazy-odds-bet-shared",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Shared MongoDB models and utilities for odds project",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -29,6 +29,7 @@
29
29
  "author": "",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
+ "bcrypt": "^5.1.1",
32
33
  "dotenv": "^16.3.1",
33
34
  "mongoose": "^8.0.0",
34
35
  "uuid": "^9.0.1"