mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			208 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const { Request, Response, NextFunction } = require('express')
 | |
| const uuidv4 = require('uuid').v4
 | |
| const Logger = require('../Logger')
 | |
| const Database = require('../Database')
 | |
| 
 | |
| /**
 | |
|  * @typedef RequestUserObject
 | |
|  * @property {import('../models/User')} user
 | |
|  *
 | |
|  * @typedef {Request & RequestUserObject} RequestWithUser
 | |
|  */
 | |
| 
 | |
| class ApiKeyController {
 | |
|   constructor() {}
 | |
| 
 | |
|   /**
 | |
|    * GET: /api/api-keys
 | |
|    *
 | |
|    * @param {RequestWithUser} req
 | |
|    * @param {Response} res
 | |
|    */
 | |
|   async getAll(req, res) {
 | |
|     const apiKeys = await Database.apiKeyModel.findAll({
 | |
|       include: [
 | |
|         {
 | |
|           model: Database.userModel,
 | |
|           attributes: ['id', 'username', 'type']
 | |
|         },
 | |
|         {
 | |
|           model: Database.userModel,
 | |
|           as: 'createdByUser',
 | |
|           attributes: ['id', 'username', 'type']
 | |
|         }
 | |
|       ]
 | |
|     })
 | |
| 
 | |
|     return res.json({
 | |
|       apiKeys: apiKeys.map((a) => a.toJSON())
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * POST: /api/api-keys
 | |
|    *
 | |
|    * @this {import('../routers/ApiRouter')}
 | |
|    *
 | |
|    * @param {RequestWithUser} req
 | |
|    * @param {Response} res
 | |
|    */
 | |
|   async create(req, res) {
 | |
|     if (!req.body.name || typeof req.body.name !== 'string') {
 | |
|       Logger.warn(`[ApiKeyController] create: Invalid name: ${req.body.name}`)
 | |
|       return res.sendStatus(400)
 | |
|     }
 | |
|     if (req.body.expiresIn && (typeof req.body.expiresIn !== 'number' || req.body.expiresIn <= 0)) {
 | |
|       Logger.warn(`[ApiKeyController] create: Invalid expiresIn: ${req.body.expiresIn}`)
 | |
|       return res.sendStatus(400)
 | |
|     }
 | |
|     if (!req.body.userId || typeof req.body.userId !== 'string') {
 | |
|       Logger.warn(`[ApiKeyController] create: Invalid userId: ${req.body.userId}`)
 | |
|       return res.sendStatus(400)
 | |
|     }
 | |
|     const user = await Database.userModel.getUserById(req.body.userId)
 | |
|     if (!user) {
 | |
|       Logger.warn(`[ApiKeyController] create: User not found: ${req.body.userId}`)
 | |
|       return res.sendStatus(400)
 | |
|     }
 | |
|     if (user.type === 'root' && !req.user.isRoot) {
 | |
|       Logger.warn(`[ApiKeyController] create: Root user API key cannot be created by non-root user`)
 | |
|       return res.sendStatus(403)
 | |
|     }
 | |
| 
 | |
|     const keyId = uuidv4() // Generate key id ahead of time to use in JWT
 | |
|     const apiKey = await Database.apiKeyModel.generateApiKey(this.auth.tokenManager.TokenSecret, keyId, req.body.name, req.body.expiresIn)
 | |
| 
 | |
|     if (!apiKey) {
 | |
|       Logger.error(`[ApiKeyController] create: Error generating API key`)
 | |
|       return res.sendStatus(500)
 | |
|     }
 | |
| 
 | |
|     // Calculate expiration time for the api key
 | |
|     const expiresAt = req.body.expiresIn ? new Date(Date.now() + req.body.expiresIn * 1000) : null
 | |
| 
 | |
|     const apiKeyInstance = await Database.apiKeyModel.create({
 | |
|       id: keyId,
 | |
|       name: req.body.name,
 | |
|       expiresAt,
 | |
|       userId: req.body.userId,
 | |
|       isActive: !!req.body.isActive,
 | |
|       createdByUserId: req.user.id
 | |
|     })
 | |
|     apiKeyInstance.dataValues.user = await apiKeyInstance.getUser({
 | |
|       attributes: ['id', 'username', 'type']
 | |
|     })
 | |
| 
 | |
|     Logger.info(`[ApiKeyController] Created API key "${apiKeyInstance.name}"`)
 | |
|     return res.json({
 | |
|       apiKey: {
 | |
|         apiKey, // Actual key only shown to user on creation
 | |
|         ...apiKeyInstance.toJSON()
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * PATCH: /api/api-keys/:id
 | |
|    * Only isActive and userId can be updated because name and expiresIn are in the JWT
 | |
|    *
 | |
|    * @param {RequestWithUser} req
 | |
|    * @param {Response} res
 | |
|    */
 | |
|   async update(req, res) {
 | |
|     const apiKey = await Database.apiKeyModel.findByPk(req.params.id, {
 | |
|       include: {
 | |
|         model: Database.userModel
 | |
|       }
 | |
|     })
 | |
|     if (!apiKey) {
 | |
|       return res.sendStatus(404)
 | |
|     }
 | |
|     // Only root user can update root user API keys
 | |
|     if (apiKey.user.type === 'root' && !req.user.isRoot) {
 | |
|       Logger.warn(`[ApiKeyController] update: Root user API key cannot be updated by non-root user`)
 | |
|       return res.sendStatus(403)
 | |
|     }
 | |
| 
 | |
|     let hasUpdates = false
 | |
|     if (req.body.userId !== undefined) {
 | |
|       if (typeof req.body.userId !== 'string') {
 | |
|         Logger.warn(`[ApiKeyController] update: Invalid userId: ${req.body.userId}`)
 | |
|         return res.sendStatus(400)
 | |
|       }
 | |
|       const user = await Database.userModel.getUserById(req.body.userId)
 | |
|       if (!user) {
 | |
|         Logger.warn(`[ApiKeyController] update: User not found: ${req.body.userId}`)
 | |
|         return res.sendStatus(400)
 | |
|       }
 | |
|       if (user.type === 'root' && !req.user.isRoot) {
 | |
|         Logger.warn(`[ApiKeyController] update: Root user API key cannot be created by non-root user`)
 | |
|         return res.sendStatus(403)
 | |
|       }
 | |
|       if (apiKey.userId !== req.body.userId) {
 | |
|         apiKey.userId = req.body.userId
 | |
|         hasUpdates = true
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (req.body.isActive !== undefined) {
 | |
|       if (typeof req.body.isActive !== 'boolean') {
 | |
|         return res.sendStatus(400)
 | |
|       }
 | |
|       if (apiKey.isActive !== req.body.isActive) {
 | |
|         apiKey.isActive = req.body.isActive
 | |
|         hasUpdates = true
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (hasUpdates) {
 | |
|       await apiKey.save()
 | |
|       apiKey.dataValues.user = await apiKey.getUser({
 | |
|         attributes: ['id', 'username', 'type']
 | |
|       })
 | |
|       Logger.info(`[ApiKeyController] Updated API key "${apiKey.name}"`)
 | |
|     } else {
 | |
|       Logger.info(`[ApiKeyController] No updates needed to API key "${apiKey.name}"`)
 | |
|     }
 | |
| 
 | |
|     return res.json({
 | |
|       apiKey: apiKey.toJSON()
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * DELETE: /api/api-keys/:id
 | |
|    *
 | |
|    * @param {RequestWithUser} req
 | |
|    * @param {Response} res
 | |
|    */
 | |
|   async delete(req, res) {
 | |
|     const apiKey = await Database.apiKeyModel.findByPk(req.params.id)
 | |
|     if (!apiKey) {
 | |
|       return res.sendStatus(404)
 | |
|     }
 | |
| 
 | |
|     await apiKey.destroy()
 | |
|     Logger.info(`[ApiKeyController] Deleted API key "${apiKey.name}"`)
 | |
| 
 | |
|     return res.sendStatus(200)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    *
 | |
|    * @param {RequestWithUser} req
 | |
|    * @param {Response} res
 | |
|    * @param {NextFunction} next
 | |
|    */
 | |
|   middleware(req, res, next) {
 | |
|     if (!req.user.isAdminOrUp) {
 | |
|       Logger.error(`[ApiKeyController] Non-admin user "${req.user.username}" attempting to access api keys`)
 | |
|       return res.sendStatus(403)
 | |
|     }
 | |
| 
 | |
|     next()
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = new ApiKeyController()
 |