{"version":3,"sources":["jsdelivr-header.js","/npm/auth-db@3.2.8/src/index.js"],"names":[],"mappings":"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","file":"/npm/auth-db@3.2.8/src/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/auth-db@3.2.8/src/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","const assert = require('assert')\nconst crypto = require('crypto')\nconst {v4: uuidv4} = require('uuid')\nconst deburr = require('lodash.deburr')\nconst emailValidator = require('email-validator')\n\nconst MIN_PASSWORD_LENGTH = 8\nconst MAX_PASSWORD_LENGTH = 60\nconst MAX_INACTIVITY_IN_SECONDS =\n  process.env.NODE_ENV === 'test' ? 1 : 60 * 60 * 24 * 10 // 10 days\nconst USERS = 'auth-db:users:'\nconst EMAILS = 'auth-db:emails:'\nconst ROLES = 'auth-db:roles:'\nconst SESSIONS = 'auth-db:sessions:'\n\nconst VERIFIED_FIELD = 'verifiedAt'\n\nconst normalize = username =>\n  deburr(username)\n      .toLowerCase()\n      .trim()\n\nfunction AuthDbError(message, code) {\n  this.name = 'AuthDbError'\n  this.message = message\n  this.code = code\n}\n\nAuthDbError.prototype = Object.create(Error.prototype)\nAuthDbError.prototype.constructor = AuthDbError\n\nmodule.exports = (redis, options) => {\n  options = options || {}\n  options = {\n    saltLength: options.saltLength || 16,\n    iterations: options.iterations || 10000,\n    keylen: options.keylen || 64,\n    digest: options.digest || 'sha256',\n    timestamp: options.timestamp !== false,\n    passwordRequired: options.passwordRequired !== false\n  }\n\n  const scanKeys = pattern => {\n    const stream = redis.scanStream({\n      match: pattern\n    })\n    return new Promise(resolve => {\n      const keys = []\n      stream.on('data', resultKeys => {\n        for (let i = 0; i < resultKeys.length; i++) {\n          keys.push(resultKeys[i])\n        }\n      })\n      stream.on('end', () => resolve(keys))\n    })\n  }\n\n  const throwError = (message, code) => {\n    return redis.unwatch().then(() => {\n      throw new AuthDbError(message, code)\n    })\n  }\n\n  const createUser = async (key, user) => {\n    let taken\n    const emails = (user.email && user.email.toLowerCase().split(',')) || []\n    for (let email of emails) {\n      email = email.trim().toLowerCase()\n      if (!emailValidator.validate(email)) {\n        return throwError(`Email ${email} is invalid`)\n      }\n      const key = EMAILS + email\n      await redis.watch(key)\n      const emailRecord = await redis.hgetall(key)\n      if (emailRecord.username) {\n        taken = email.trim()\n        break\n      }\n    }\n    if (taken) {\n      return throwError('Email ' + taken + ' already taken')\n    }\n    if (options.timestamp) {\n      user.createdAt = new Date().toISOString()\n      user.updatedAt = user.createdAt\n    }\n    let transaction = redis.multi().hmset(key, user)\n    emails.forEach(email => {\n      const key = EMAILS + email.trim()\n      transaction = transaction.hsetnx(key, 'username', user.username)\n    })\n    const res = await transaction.exec()\n    return res !== null || throwError('User lock error')\n  }\n\n  const addEmail = async (email, username) => {\n    let key = USERS + username\n    await redis.watch(key)\n    const user = await redis.hgetall(key)\n    if (user.email) {\n      user.email = user.email\n          .split(',')\n          .concat(email)\n          .join(',')\n    } else {\n      user.email = email\n    }\n\n    let transaction = redis.multi().hmset(key, user)\n    key = EMAILS + email.trim()\n    transaction = transaction.hsetnx(key, 'username', username)\n\n    const res = await transaction.exec()\n    return res !== null || (await throwError('User/email lock error'))\n  }\n\n  const removeEmail = async (email, username) => {\n    let key = USERS + username\n    await redis.watch(key)\n    const user = await redis.hgetall(key)\n    const emails = (user.email && user.email.split(',')) || []\n    const index = emails.indexOf(email)\n    if (index > -1) {\n      emails.splice(index, 1)\n    }\n    user.email = emails.join(',')\n\n    let transaction = redis.multi().hmset(key, user)\n    key = EMAILS + email.trim()\n    transaction = transaction.del(key)\n\n    const res = await transaction.exec()\n    return res !== null || (await throwError('User/email lock error'))\n  }\n\n  const saveRole = (key, role, acl) => {\n    let transaction = redis.multi().hmset(key, role)\n    if (acl && acl.length > 0) {\n      transaction = transaction\n          .del(key + ':acl')\n          .sadd(key + ':acl', aclToSet(acl))\n    }\n    return transaction\n        .exec()\n        .then(res => res !== null || throwError('Role lock error', res))\n  }\n\n  return {\n    users: {\n      get: function(username) {\n        return redis.hgetall(USERS + normalize(username)).then(user => {\n          if (user.roles) {\n            user.roles = user.roles.split(',')\n          }\n          delete user.password\n          delete user.salt\n          return user\n        })\n      },\n      async create(user) {\n        assert(user.username, 'Missing username')\n        user = Object.assign({}, user)\n        if (options.passwordRequired) {\n          assert(user.password, 'Missing password')\n        }\n        if (user.password) {\n          user = await encryptPassword(user, options)\n        }\n        user.username = normalize(user.username)\n        const key = USERS + user.username\n        await redis.watch(key)\n        const res = await redis.hmget(key, 'username')\n        await (res[0] === null\n          ? createUser(key, user)\n          : throwError('User name already taken', 'userExists'))\n        return user.username\n      },\n      update: async (user, username) => {\n        assert(username, 'Missing username')\n        assert(!user.email, 'To update/create/delete a email use email api')\n        user = Object.assign({}, user)\n        if (user.password) {\n          user = await encryptPassword(user, options)\n        }\n        const key = USERS + normalize(username)\n        await redis.watch(key)\n        let record = await redis.hgetall(key)\n        const found = Boolean(record.username)\n        if (!found) {\n          return await throwError('User not found')\n        }\n        if (options.timestamp) {\n          user.updatedAt = new Date().toISOString()\n        }\n        record = Object.assign(record, user)\n        const res = await redis\n            .multi()\n            .hmset(key, record)\n            .exec()\n        return res !== null || (await throwError('User update lock error'))\n      },\n      remove: async username => {\n        assert(username, 'Missing username')\n        const userKey = USERS + normalize(username)\n        await redis.watch(userKey)\n        const [requests, email] = await redis.hmget(\n            userKey,\n            'requests',\n            'email'\n        )\n        if (Number(requests) > 0) {\n          await throwError('User cannot be removed', 'activeUser')\n        }\n        const emails = email ? email.split(',') : []\n        const multi = await redis.multi()\n        for (const email of emails) {\n          multi.del(EMAILS + email.trim())\n        }\n        const res = await multi.del(userKey).exec()\n        return res !== null || (await throwError('Remove user lock error'))\n      },\n      checkPassword: function(credentials) {\n        return redis\n            .hgetall(USERS + credentials.username.toLowerCase())\n            .then(user =>\n              hashPassword(credentials.password, user.salt, options).then(\n                  password => password === user.password\n              )\n            )\n      },\n      emails: async username => {\n        username = normalize(username)\n        const user = await redis.hgetall(USERS + username)\n        const emailList =\n          (user.email && user.email.toLowerCase().split(',')) || []\n        const emails = []\n        for (let email of emailList) {\n          email = email.trim()\n          const key = EMAILS + email\n          const emailRecord = await redis.hgetall(key)\n          if (emailRecord.username && emailRecord.username === username) {\n            emails.push(Object.assign({}, emailRecord, {email}))\n          }\n        }\n        return emails\n      }\n    },\n    email: {\n      get: function(email) {\n        return redis.hgetall(EMAILS + email.toLowerCase())\n      },\n      add: async (email, username) => {\n        email = email.trim().toLowerCase()\n        if (!emailValidator.validate(email)) {\n          return await throwError(`Email ${email} is invalid`)\n        }\n        const key = EMAILS + email\n        await redis.watch(key)\n        const record = await redis.hgetall(key)\n        const found = Boolean(record.username)\n        return await (found\n          ? throwError(`Email ${email} already exist`)\n          : addEmail(email, normalize(username)))\n      },\n      async setVerified(email) {\n        const key = EMAILS + email\n        let verified = await redis.hget(key, VERIFIED_FIELD)\n        if (!verified) {\n          verified = new Date().toISOString()\n          await redis.hmset(key, VERIFIED_FIELD, verified)\n        }\n        return verified\n      },\n      update: function(data, email) {\n        return Promise.resolve().then(function() {\n          assert(email, 'Missing email')\n          email = email.trim().toLowerCase()\n          const key = EMAILS + email\n          return redis.watch(key).then(() =>\n            redis.hgetall(key).then(record => {\n              const found = record.username\n              if (!found) {\n                return throwError('Email not found')\n              }\n              if (record.username !== data.username) {\n                return throwError('User name is missing or do not match')\n              }\n              record = Object.assign(record, data)\n              return redis\n                  .multi()\n                  .hmset(key, record)\n                  .exec()\n                  .then(\n                      res => res !== null || throwError('Email update lock error')\n                  )\n            })\n          )\n        })\n      },\n      remove: function(email, username) {\n        return Promise.resolve().then(function() {\n          email = email.trim().toLowerCase()\n          username = username.toLowerCase()\n          if (!emailValidator.validate(email)) {\n            return throwError(`Email ${email} is invalid`)\n          }\n          const key = EMAILS + email\n          return redis.watch(key).then(() =>\n            redis.hgetall(key).then(record => {\n              const found = record.username\n              if (!found) {\n                return throwError(`Email ${email} not found`)\n              }\n              if (record.username !== username) {\n                return throwError(\n                    `Email ${email} not registered for user ${username}`\n                )\n              }\n              return removeEmail(email, username)\n            })\n          )\n        })\n      }\n    },\n    roles: {\n      get: function(name) {\n        const key = ROLES + name.toLowerCase()\n        return redis.hgetall(key).then(function(record) {\n          return record.name\n            ? redis.smembers(key + ':acl').then(function(res) {\n              if (res) {\n                record.acl = setToAcl(res)\n              }\n              return record\n            })\n            : record\n        })\n      },\n      list: function(prefix) {\n        prefix = prefix || ''\n        return scanKeys(`${ROLES}${prefix.toLowerCase()}*`).then(keys =>\n          keys\n              .filter(key => key.indexOf(':acl') === -1)\n              .map(key => key.replace(ROLES, ''))\n        )\n      },\n      create: function(role) {\n        return Promise.resolve().then(function() {\n          assert(role.name, 'Role name is missing')\n          assert(\n              role.acl === void 0 || Array.isArray(role.acl),\n              'acl must be an array'\n          )\n          role = Object.assign({}, role)\n          const acl = role.acl\n          delete role.acl\n          const key = ROLES + role.name.toLowerCase()\n          return redis\n              .watch(key)\n              .then(() =>\n                redis\n                    .hmget(key, 'name')\n                    .then(res =>\n                  res[0] === null\n                    ? saveRole(key, role, acl)\n                    : throwError('Role already exists')\n                    )\n              )\n        })\n      },\n      update: function(role, name) {\n        return Promise.resolve().then(function() {\n          assert(name, 'Role name is missing')\n          assert(\n              role.acl === void 0 || Array.isArray(role.acl),\n              'acl must be an array'\n          )\n          role = Object.assign({}, role)\n          const acl = role.acl\n          delete role.acl\n          name = name.toLowerCase()\n          const key = ROLES + name\n          return redis.watch(key).then(() =>\n            redis.hgetall(key).then(record => {\n              const found = record.name && record.name.toLowerCase() === name\n              if (!found) {\n                return throwError('Role not found')\n              }\n              record = Object.assign(record, role)\n              return saveRole(key, record, acl)\n            })\n          )\n        })\n      },\n      hasPermission: function(roles, resource, method) {\n        const checkRole = name => {\n          if (typeof name !== 'string') {\n            return false\n          }\n          const key = ROLES + name.toLowerCase() + ':acl'\n          return typeof method === 'string'\n            ? redis\n                .sismember(key, resource + method.toUpperCase())\n                .then(res => res || redis.sismember(key, resource + '*'))\n                .then(res => res === 1)\n            : redis.sismember(key, resource + '*').then(res => res === 1)\n        }\n\n        resource = resource.toLowerCase() + ':'\n        roles = Array.isArray(roles) ? roles : [roles]\n\n        return roles.reduce(\n            (promise, role) =>\n              promise.then(res => (res === true ? res : checkRole(role))),\n            Promise.resolve(false)\n        )\n      }\n    },\n    sessions: {\n      get(username, id) {\n        return redis.hgetall(SESSIONS + `${username}:${id}`)\n      },\n      async create(\n          username,\n          data = {},\n          maxInactivityInSeconds = MAX_INACTIVITY_IN_SECONDS\n      ) {\n        const id = uuidv4()\n        const sessionKey = SESSIONS + `${username}:${id}`\n        data = Object.assign({createdAt: new Date().toISOString()}, data)\n        const res = await redis.hmset(sessionKey, data)\n        await redis.expire(sessionKey, maxInactivityInSeconds)\n        return res === 'OK' ? id : null\n      },\n      async validate(\n          username,\n          id,\n          maxInactivityInSeconds = MAX_INACTIVITY_IN_SECONDS,\n          fields\n      ) {\n        const now = Date.now()\n        const userKey = USERS + username\n        const licenseExpireOn = await redis.hget(userKey, 'expireOn')\n        if (licenseExpireOn && now > Number(licenseExpireOn)) {\n          throw new AuthDbError('User license expired', 'licenseExpired')\n        }\n        const sessionKey = `${SESSIONS}${username}:${id}`\n        if (!(await redis.exists(sessionKey))) {\n          throw new AuthDbError('Session not found', 'notFound')\n        }\n        await redis.expire(sessionKey, maxInactivityInSeconds)\n        await redis.hincrby(userKey, 'requests', 1)\n        await redis.hincrby(sessionKey, 'requests', 1)\n        await redis.hmset(userKey, 'lastRequest', now)\n        await redis.hmset(sessionKey, 'lastRequest', now)\n        if (Array.isArray(fields)) {\n          return await redis.hmget(sessionKey, ...fields)\n        }\n      },\n      async destroy(username, id) {\n        return (await redis.del(SESSIONS + `${username}:${id}`)) === 1\n      },\n      reset(username) {\n        return scanKeys(`${SESSIONS}${username}:*`)\n            .then(keys => Promise.all(keys.map(key => redis.del(key))))\n            .then(() => true)\n      }\n    }\n  }\n}\n\nfunction encryptPassword(user, options) {\n  return Promise.resolve().then(() => {\n    assert(\n        user.password.length >= MIN_PASSWORD_LENGTH,\n        'password should have a minimum of ' + MIN_PASSWORD_LENGTH + ' characters'\n    )\n    assert(\n        user.password.length <= MAX_PASSWORD_LENGTH,\n        'password should have a maximum of ' + MAX_PASSWORD_LENGTH + ' characters'\n    )\n    user.salt = crypto.randomBytes(options.saltLength).toString('base64')\n    return hashPassword(user.password, user.salt, options).then(password => {\n      user.password = password\n      return user\n    })\n  })\n}\n\nfunction hashPassword(password, salt, options) {\n  return new Promise((resolve, reject) => {\n    crypto.pbkdf2(\n        password,\n        salt,\n        options.iterations,\n        options.keylen,\n        options.digest,\n        (error, key) => {\n          if (error) {\n            return reject(error)\n          }\n          resolve(key.toString('base64'))\n        }\n    )\n  })\n}\n\nfunction aclToSet(acl) {\n  const set = []\n  acl.forEach(function(aci) {\n    if (typeof aci === 'string') {\n      aci = {resource: aci}\n    }\n    assert(aci.resource, 'Resource must be informed')\n    aci.methods = aci.methods || ['*']\n    aci.methods = Array.isArray(aci.methods) ? aci.methods : [aci.methods]\n    aci.methods.forEach(function(method) {\n      set.push(aci.resource.toLowerCase() + ':' + method.toUpperCase())\n    })\n  })\n  return set\n}\n\nfunction setToAcl(set) {\n  const acl = []\n  set.forEach(function(str) {\n    const elements = str.split(':')\n    const aci = acl.filter(obj => obj.resource === elements[0])\n    if (aci.length === 0) {\n      acl.push({\n        resource: elements[0],\n        methods: [elements[1]]\n      })\n    } else {\n      aci[0].methods.push(elements[1])\n    }\n  })\n  return acl\n}\n"]}