mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 01:41:35 +00:00
170 lines
4.3 KiB
JavaScript
170 lines
4.3 KiB
JavaScript
const { DataTypes, Model } = require('sequelize')
|
|
const crypto = require('crypto')
|
|
const Logger = require('../Logger')
|
|
|
|
const ALGORITHM = 'aes-256-cbc'
|
|
|
|
function getEncryptionKey() {
|
|
const secret = process.env.AUDIBLE_ENCRYPTION_KEY || process.env.TOKEN_SECRET
|
|
if (!secret) throw new Error('[AudibleAccount] No encryption key available — set AUDIBLE_ENCRYPTION_KEY or TOKEN_SECRET')
|
|
return crypto.scryptSync(secret, 'audible-salt', 32)
|
|
}
|
|
|
|
function encrypt(text) {
|
|
if (!text) return null
|
|
const iv = crypto.randomBytes(16)
|
|
const cipher = crypto.createCipheriv(ALGORITHM, getEncryptionKey(), iv)
|
|
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
|
|
return iv.toString('hex') + ':' + encrypted.toString('hex')
|
|
}
|
|
|
|
function decrypt(data) {
|
|
if (!data) return null
|
|
try {
|
|
const [ivHex, encHex] = data.split(':')
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, getEncryptionKey(), Buffer.from(ivHex, 'hex'))
|
|
const decrypted = Buffer.concat([decipher.update(Buffer.from(encHex, 'hex')), decipher.final()])
|
|
return decrypted.toString('utf8')
|
|
} catch (err) {
|
|
Logger.error('[AudibleAccount] Failed to decrypt token', err.message)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef ClientAudibleAccount
|
|
* @property {string} id
|
|
* @property {string} userId
|
|
* @property {string} email
|
|
* @property {string} region
|
|
* @property {Date|null} lastSync
|
|
* @property {boolean} isActive
|
|
* @property {Date} createdAt
|
|
*/
|
|
|
|
class AudibleAccount extends Model {
|
|
constructor(values, options) {
|
|
super(values, options)
|
|
|
|
/** @type {string} */
|
|
this.id
|
|
/** @type {string} */
|
|
this.userId
|
|
/** @type {string} */
|
|
this.email
|
|
/** @type {string} */
|
|
this.region
|
|
/** @type {string} encrypted access token */
|
|
this.encryptedToken
|
|
/** @type {string|null} encrypted website cookies JSON */
|
|
this.encryptedCookies
|
|
/** @type {string|null} library to show the preorders shelf on */
|
|
this.libraryId
|
|
/** @type {number} position in the shelf list (0 = first) */
|
|
this.shelfPosition
|
|
/** @type {Date|null} */
|
|
this.lastSync
|
|
/** @type {boolean} */
|
|
this.isActive
|
|
/** @type {Date} */
|
|
this.createdAt
|
|
/** @type {Date} */
|
|
this.updatedAt
|
|
}
|
|
|
|
getAccessToken() {
|
|
return decrypt(this.encryptedToken)
|
|
}
|
|
|
|
setAccessToken(token) {
|
|
this.encryptedToken = encrypt(token)
|
|
}
|
|
|
|
getWebsiteCookies() {
|
|
const raw = decrypt(this.encryptedCookies)
|
|
if (!raw) return null
|
|
try { return JSON.parse(raw) } catch { return null }
|
|
}
|
|
|
|
setWebsiteCookies(cookiesObj) {
|
|
this.encryptedCookies = cookiesObj ? encrypt(JSON.stringify(cookiesObj)) : null
|
|
}
|
|
|
|
/**
|
|
* @returns {ClientAudibleAccount}
|
|
*/
|
|
toClientJson() {
|
|
return {
|
|
id: this.id,
|
|
userId: this.userId,
|
|
email: this.email,
|
|
region: this.region,
|
|
libraryId: this.libraryId || null,
|
|
shelfPosition: this.shelfPosition || 0,
|
|
lastSync: this.lastSync,
|
|
isActive: this.isActive,
|
|
createdAt: this.createdAt
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('../Database').sequelize} sequelize
|
|
*/
|
|
static init(sequelize) {
|
|
super.init(
|
|
{
|
|
id: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
email: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false
|
|
},
|
|
region: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
defaultValue: 'us'
|
|
},
|
|
encryptedToken: {
|
|
type: DataTypes.TEXT,
|
|
allowNull: false
|
|
},
|
|
encryptedCookies: {
|
|
type: DataTypes.TEXT,
|
|
allowNull: true
|
|
},
|
|
libraryId: {
|
|
type: DataTypes.UUID,
|
|
allowNull: true
|
|
},
|
|
shelfPosition: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0
|
|
},
|
|
lastSync: DataTypes.DATE,
|
|
isActive: {
|
|
type: DataTypes.BOOLEAN,
|
|
allowNull: false,
|
|
defaultValue: true
|
|
}
|
|
},
|
|
{
|
|
sequelize,
|
|
modelName: 'audibleAccount',
|
|
indexes: [
|
|
{ fields: ['userId'] },
|
|
{ unique: true, fields: ['email', 'userId'] }
|
|
]
|
|
}
|
|
)
|
|
|
|
const { user } = sequelize.models
|
|
user.hasMany(AudibleAccount, { onDelete: 'CASCADE' })
|
|
AudibleAccount.belongsTo(user)
|
|
}
|
|
}
|
|
|
|
module.exports = AudibleAccount
|