33 lines
1.7 KiB
JavaScript
33 lines
1.7 KiB
JavaScript
'use strict';
|
|
|
|
const mongoose = require('mongoose'), Schema = mongoose.Schema;
|
|
const { ApiKeyServices } = require('../helpers/constants');
|
|
|
|
/**
|
|
* ApiKey model — stores hashed API keys for external data-export consumers.
|
|
*
|
|
* Flow:
|
|
* 1. POST /api/keys → generate random key, store prefix (first 8 chars) + bcrypt hash, return plain key ONCE.
|
|
* 2. Subsequent requests supply X-API-Key header → middleware does prefix lookup + bcrypt.compare.
|
|
* 3. On match, middleware sets req.uid = key.owner so all existing ownership filters work unchanged.
|
|
*
|
|
* Security notes:
|
|
* - Plain key is NEVER stored. Only the bcrypt hash is persisted.
|
|
* - Key prefix (first 8 chars) is stored in clear text for efficient DB lookup before comparing hashes.
|
|
* - lastUsedAt is updated async (fire-and-forget) to avoid adding latency to the request path.
|
|
*/
|
|
const schema = new Schema({
|
|
owner: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
|
|
label: { type: String, required: true, trim: true, maxlength: 100 },
|
|
prefix: { type: String, required: true, index: true, maxlength: 8 }, // first 8 chars of plain key, stored for O(1) candidate lookup
|
|
keyHash: { type: String, required: true }, // bcrypt hash of the full plain key
|
|
service: { type: String, enum: Object.values(ApiKeyServices), default: ApiKeyServices.DATA_EXPORT, index: true }, // which service/integration this key grants access to
|
|
active: { type: Boolean, default: true, index: true },
|
|
managedBy: { type: String, enum: ['owner', 'admin'], default: 'owner' },
|
|
createdAt: { type: Date, default: Date.now },
|
|
lastUsedAt: { type: Date },
|
|
requestCount: { type: Number, default: 0 }
|
|
});
|
|
|
|
module.exports = mongoose.model('ApiKey', schema);
|