Spaces:
Running
Running
File size: 2,688 Bytes
6e13570 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const enrolledCourseSchema = new mongoose.Schema(
{
course: { type: mongoose.Schema.Types.ObjectId, ref: "Course", required: true },
order: { type: mongoose.Schema.Types.ObjectId, ref: "Order", required: true },
accessGrantedAt: { type: Date, default: Date.now },
},
{ _id: false }
);
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true, trim: true, maxlength: 120 },
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
maxlength: 254,
index: true,
},
passwordHash: { type: String, required: true, select: false },
role: { type: String, enum: ["user", "admin"], default: "user" },
// Access-control / session-security bookkeeping
tokenVersion: { type: Number, default: 0 }, // bump to invalidate all issued JWTs
activeRefreshTokens: {
// store only the jti + expiry, never the raw token, so a leaked DB
// dump can't be replayed as a session
type: [
{
jti: String,
expiresAt: Date,
userAgent: String,
ip: String,
},
],
default: [],
select: false,
},
// Brute-force protection
failedLoginAttempts: { type: Number, default: 0, select: false },
lockUntil: { type: Date, default: null, select: false },
enrolledCourses: { type: [enrolledCourseSchema], default: [] },
isActive: { type: Boolean, default: true },
},
{ timestamps: true }
);
userSchema.methods.setPassword = async function setPassword(plainPassword) {
const salt = await bcrypt.genSalt(12);
this.passwordHash = await bcrypt.hash(plainPassword, salt);
};
userSchema.methods.comparePassword = function comparePassword(plainPassword) {
return bcrypt.compare(plainPassword, this.passwordHash);
};
userSchema.methods.isLocked = function isLocked() {
return !!(this.lockUntil && this.lockUntil > Date.now());
};
userSchema.methods.hasAccessToCourse = function hasAccessToCourse(courseId) {
return this.enrolledCourses.some((ec) => ec.course.toString() === courseId.toString());
};
userSchema.methods.toSafeJSON = function toSafeJSON() {
return {
id: this._id,
name: this.name,
email: this.email,
role: this.role,
enrolledCourses: this.enrolledCourses.map((ec) => ({
course: ec.course,
order: ec.order,
accessGrantedAt: ec.accessGrantedAt,
})),
createdAt: this.createdAt,
};
};
module.exports = mongoose.model("User", userSchema);
|