- Add session-based authentication for web-UI (Login.vue, AuthController) - Add AuthSessionMiddleware: redirects unauthenticated users to /login - Add ApiKeyMiddleware: validates X-Api-Key header on all /api/v1 routes - Add BasicAuthMiddleware: registered for future M2M use - Use timingSafeEqual for all credential comparisons (timing attack prevention) - Regenerate session ID on login (session fixation prevention) - Validate redirect target after login (open redirect prevention) - Require WEBUI_USERNAME, WEBUI_PASSWORD, API_KEY as mandatory env vars - Share auth.username/authenticated via Inertia shared props - Add user dropdown in navbar (avatar icon + username + logout) - Add login i18n keys in fr.json and en.json - Remove orphan SQL migrations (users, auth_access_tokens) and @adonisjs/lucid
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import {
|
|
type Db,
|
|
type Collection,
|
|
ObjectId,
|
|
type Filter,
|
|
type UpdateFilter,
|
|
type Sort,
|
|
} from 'mongodb'
|
|
import { getMongoDb } from '#providers/mongodb_provider'
|
|
|
|
export abstract class BaseModel {
|
|
static collectionName: string
|
|
|
|
_id?: ObjectId
|
|
createdAt?: Date
|
|
updatedAt?: Date
|
|
|
|
static getDb(): Db {
|
|
return getMongoDb()
|
|
}
|
|
|
|
static getCollection(this: { collectionName: string; getDb(): Db }): Collection {
|
|
return this.getDb().collection(this.collectionName)
|
|
}
|
|
|
|
static async find(
|
|
this: { collectionName: string; getDb(): Db },
|
|
filter: Filter<any> = {}
|
|
): Promise<any[]> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
return collection.find(filter).toArray()
|
|
}
|
|
|
|
static async findById(this: { collectionName: string; getDb(): Db }, id: string | ObjectId): Promise<any | null> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const objectId = typeof id === 'string' ? new ObjectId(id) : id
|
|
return collection.findOne({ _id: objectId })
|
|
}
|
|
|
|
static async findOne(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<any | null> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
return collection.findOne(filter)
|
|
}
|
|
|
|
static async create(this: { collectionName: string; getDb(): Db }, data: Partial<any>): Promise<any> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const now = new Date()
|
|
const doc = { ...data, createdAt: now, updatedAt: now }
|
|
const result = await collection.insertOne(doc)
|
|
return { ...doc, _id: result.insertedId }
|
|
}
|
|
|
|
static async createMany(this: { collectionName: string; getDb(): Db }, data: Partial<any>[]): Promise<any[]> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const now = new Date()
|
|
const docs = data.map((d) => ({ ...d, createdAt: now, updatedAt: now }))
|
|
const result = await collection.insertMany(docs)
|
|
return docs.map((doc, i) => ({ ...doc, _id: result.insertedIds[i] }))
|
|
}
|
|
|
|
static async updateOne(
|
|
this: { collectionName: string; getDb(): Db },
|
|
filter: Filter<any>,
|
|
update: UpdateFilter<any> | Partial<any>
|
|
): Promise<any> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const updateDoc = '$set' in update ? update : { $set: { ...update, updatedAt: new Date() } }
|
|
return collection.updateOne(filter, updateDoc)
|
|
}
|
|
|
|
static async updateMany(
|
|
this: { collectionName: string; getDb(): Db },
|
|
filter: Filter<any>,
|
|
update: UpdateFilter<any> | Partial<any>
|
|
): Promise<any> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const updateDoc = '$set' in update ? update : { $set: { ...update, updatedAt: new Date() } }
|
|
return collection.updateMany(filter, updateDoc)
|
|
}
|
|
|
|
static async deleteOne(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<boolean> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const result = await collection.deleteOne(filter)
|
|
return result.deletedCount === 1
|
|
}
|
|
|
|
static async deleteMany(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<number> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const result = await collection.deleteMany(filter)
|
|
return result.deletedCount
|
|
}
|
|
|
|
static async paginate(
|
|
this: { collectionName: string; getDb(): Db },
|
|
page: number,
|
|
perPage: number,
|
|
filter: Filter<any> = {},
|
|
sort: Sort = {}
|
|
): Promise<{ data: any[]; total: number; page: number; perPage: number; lastPage: number }> {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
const total = await collection.countDocuments(filter)
|
|
const lastPage = Math.max(1, Math.ceil(total / perPage))
|
|
const skip = (page - 1) * perPage
|
|
const data = await collection.find(filter).sort(sort).skip(skip).limit(perPage).toArray()
|
|
return { data, total, page, perPage, lastPage }
|
|
}
|
|
|
|
static query(this: { collectionName: string; getDb(): Db }) {
|
|
const collection = this.getDb().collection(this.collectionName)
|
|
return collection
|
|
}
|
|
|
|
async save(): Promise<void> {
|
|
const constructor = this.constructor as typeof BaseModel
|
|
const collection = constructor.getDb().collection(constructor.collectionName)
|
|
this.updatedAt = new Date()
|
|
if (!this.createdAt) {
|
|
this.createdAt = new Date()
|
|
}
|
|
const { _id, ...data } = this as any
|
|
if (_id) {
|
|
await collection.updateOne({ _id }, { $set: data })
|
|
} else {
|
|
const result = await collection.insertOne(data)
|
|
this._id = result.insertedId
|
|
}
|
|
}
|
|
|
|
async delete(): Promise<boolean> {
|
|
const constructor = this.constructor as typeof BaseModel
|
|
const collection = constructor.getDb().collection(constructor.collectionName)
|
|
if (!this._id) return false
|
|
const result = await collection.deleteOne({ _id: this._id })
|
|
return result.deletedCount === 1
|
|
}
|
|
|
|
async refresh(): Promise<void> {
|
|
const constructor = this.constructor as typeof BaseModel
|
|
const collection = constructor.getDb().collection(constructor.collectionName)
|
|
if (!this._id) return
|
|
const doc = await collection.findOne({ _id: this._id })
|
|
if (doc) {
|
|
Object.assign(this, doc)
|
|
}
|
|
}
|
|
}
|