qwgit / lib / auth.js
http{/,s} git server
git clone http://git.nthia.dev/qwgit

let { nextTick } = process
module.exports = Auth

function Auth(data) {
  if (!(this instanceof Auth)) return new Auth(data)
  this.data = []
  this._groups = new Map
  if (Array.isArray(data)) this.load(data)
}

Auth.prototype.clear = function () {
  this.data = []
}

Auth.prototype.load = function (data) {
  data.forEach(creds => this.add(creds))
}

Auth.prototype.add = function (d) {
  d = Object.assign({}, d)
  if (!Array.isArray(d.logins)) d.logins = []
  if (!Array.isArray(d.ids)) d.ids = []
  if (!Array.isArray(d.groups)) d.groups = []
  d.ids = d.ids.map(id => id === '/' ? '/' : id.replace(/^\//, ''))
  d.logins = d.logins.map(l => {
    l = Object.assign({}, l)
    if (l.fingerprint512 !== undefined) l.fingerprint512 = normfp(l.fingerprint512)
    return l
  })
  d.groups.forEach(g => {
    let groups = this._groups.get(g) ?? new Set
    d.ids.forEach(id => groups.add(id))
    this._groups.set(g, groups)
  })
  this.data.push(d)
}

Auth.prototype.challenge = function (id, creds, cb) {
  if (creds.fingerprint512 !== undefined) {
    creds = Object.assign({}, creds, { fingerprint512: normfp(creds.fingerprint512) })
  }
  if (typeof id === 'number') id = String(id)
  for (let i = 0; i < this.data.length; i++) {
    let d = this.data[i]
    if (!checkId(id, d.ids)) continue
    for (let j = 0; j < d.logins.length; j++) {
      let l = d.logins[j]
      if (l.type === 'fingerprint') {
        if (l.fingerprint512 === undefined) continue
        if (l.fingerprint512 === creds.fingerprint512) {
          return nextTick(cb, null, d)
        }
      }
    }
  }
  nextTick(cb, null, null)
}

Auth.prototype.find = function (creds, cb) {
  if (creds.fingerprint512 !== undefined) {
    creds = Object.assign({}, creds, { fingerprint512: normfp(creds.fingerprint512) })
  }
  let ids = []
  for (let i = 0; i < this.data.length; i++) {
    let d = this.data[i]
    for (let j = 0; j < d.logins.length; j++) {
      let l = d.logins[j]
      if (l.type === 'fingerprint') {
        if (l.fingerprint512 === undefined) continue
        if (l.fingerprint512 === creds.fingerprint512) {
          ids = ids.concat(d.ids)
        }
      }
    }
  }
  nextTick(cb, null, ids)
}

function normfp(x) { return String(x).toLowerCase().replace(/:/g,'') }
function checkId(id, ids) {
  if (ids.includes(id)) return true
  for (let i of ids) {
    if (i === '/') return true
    if (i.endsWith('/') && id.startsWith(i)) return true
  }
  return false
}