<commit_before>{EventEmitter} = require 'microemitter'

module.exports = class AuthWorker extends EventEmitter

  AuthedClient = require './authedclient'

  AUTH_EXCHANGE_OPTIONS =
    type        : 'fanout'
    autoDelete  : yes

  constructor:(@bongo, @resourceName, @presenceExchange='services-presence')->
    @services = {}
    @clients  = {
      bySocketId    : {}
      byExchange    : {}
      byRoutingKey  : {}
    }
    @counts   = {}

  bound: require 'koding-bound'

  authenticate:(messageData, routingKey, callback)->
    {clientId, channel, event} = messageData
    @requireSession clientId, routingKey, callback

  requireSession:(clientId, routingKey, callback)->
    {JSession} = @bongo.models
    JSession.fetchSession clientId, (err, session)=>
      if err? or not session? then @rejectClient routingKey
      else
        tokenHasChanged = session.clientId isnt clientId
        @updateSessionToken session.clientId, routingKey  if tokenHasChanged
        callback session

  updateSessionToken:(clientId, routingKey)->
    @bongo.respondToClient routingKey,
      method      : 'updateSessionToken'
      arguments   : [clientId]
      callbacks   : {}

  getNextServiceName:(serviceType)->
    count = @counts[serviceType] ?= 0
    servicesOfType = @services[serviceType]
    return  unless servicesOfType?.length
    serviceName = servicesOfType[count % servicesOfType.length]
    @counts[serviceType] += 1
    return serviceName

  addService:({serviceGenericName, serviceUniqueName})->
    servicesOfType = @services[serviceGenericName] ?= []
    servicesOfType.push serviceUniqueName 

  removeService:({serviceGenericName, serviceUniqueName})->
    servicesOfType = @services[serviceGenericName] 
    index = servicesOfType.indexOf serviceUniqueName
    servicesOfType.splice index, 1
    clientsByExchange = @clients.byExchange[serviceUniqueName]
    clientsByExchange?.forEach @bound 'cycleClient'

  cycleClient:(client)->
    {routingKey} = client
    @bongo.respondToClient routingKey, {
      method      : 'cycleChannel'
      arguments   : []
      callbacks   : {}
    }

  removeClient:(rest...)->
    if rest.length is 1
      [client] = rest
      return @removeClient client.socketId, client.exchange, client.routingKey
    [socketId, exchange, routingKey] = rest
    delete @clients.bySocketId[socketId]
    delete @clients.byExchange[exchange]
    delete @clients.byRoutingKey[routingKey]

  addClient:(socketId, exchange, routingKey, sendOk=yes)->
    if sendOk
      @bongo.respondToClient routingKey, {
        method    : 'authOk'
        arguments : []
        callbacks : {}
      }
    clientsBySocketId   = @clients.bySocketId[socketId]     ?= []
    clientsByExchange   = @clients.byExchange[exchange]     ?= []
    clientsByRoutingKey = @clients.byRoutingKey[routingKey] ?= []
    client = new AuthedClient {routingKey, socketId, exchange}
    clientsBySocketId.push client
    clientsByRoutingKey.push client
    clientsByExchange.push client

  rejectClient:(routingKey, message)->
    return console.trace()  unless routingKey?
    @bongo.respondToClient routingKey, {
      method    : 'error'
      arguments : [message: message ? 'Access denied']
      callbacks : {}
    }

  joinClient: do ->

    joinClientHelper =(messageData, routingKey, socketId)->
      @authenticate messageData, routingKey, (session)=>
        serviceResourceName = @getNextServiceName messageData.name
        unless serviceResourceName?
          @bongo.respondToClient routingKey, {
            method    : 'error'
            arguments : [message: 'Service unavailable!', code:503]
            callbacks : {}
          }
        else
          {connection} = @bongo.mq
          connection.exchange serviceResourceName, AUTH_EXCHANGE_OPTIONS,
            (exchange)=>
              exchange.publish 'auth.join', {
                username    : session.username
                routingKey  : routingKey
              }
              exchange.close() # don't leak a channel
              @addClient socketId, exchange.name, routingKey

    ensureGroupPermission =(group, account, roles, callback)->
      {JPermissionSet, JGroup} = @bongo.models
      client = {context: group.slug, connection: delegate: account}
      JPermissionSet.checkPermission client, "read activity", group,
        (err, hasPermission)->
          if err then callback err
          else unless hasPermission
            callback {message: 'Access denied!', code: 403}
          else
            JGroup.fetchSecretChannelName group.slug, callback

    joinClientGroupHelper =(messageData, routingKey, socketId)->
      {JAccount, JGroup} = @bongo.models
      fail = (err)=>
        console.error err  if err
        @rejectClient routingKey
      @authenticate messageData, routingKey, (session)=>
        unless session then fail()
        else JAccount.one {'profile.nickname': session.username},
          (err, account)=>
            if err or not account then fail err
            else JGroup.one {slug: messageData.group}, (err, group)=>
              if err or not group then fail err
              else 
                group.fetchRolesByAccount account, (err, roles)=>
                  if err or not roles then fail err
                  else
                    ensureGroupPermission.call this, group, account, roles,
                      (err, secretChannelName)=>
                        if err or not secretChannelName
                          @rejectClient routingKey
                        else
                          setSecretNameEvent = "#{routingKey}.setSecretName"
                          message = JSON.stringify secretChannelName
                          @bongo.respondToClient setSecretNameEvent, message

    joinClient =(messageData, socketId)->
      {channel, routingKey, serviceType} = messageData
      switch serviceType
        when 'bongo', 'kite'
          joinClientHelper.call this, messageData, routingKey, socketId
        when 'group'
          console.log {routingKey}
          unless ///^group.#{messageData.group}///.test routingKey
            console.log 'rejecting', routingKey
            return @rejectClient routingKey
          joinClientGroupHelper.call this, messageData, routingKey, socketId
        when 'secret'
          @addClient socketId, routingKey, routingKey, no
        else
          @rejectClient routingKey

  cleanUpClient:(client)->
    @removeClient client
    @bongo.mq.connection.exchange client.exchange, AUTH_EXCHANGE_OPTIONS,
      (exchange)->
        exchange.publish 'auth.leave', {
          routingKey: client.routingKey
        }
        exchange.close() # don't leak a channel!

  cleanUpAfterDisconnect:(socketId)->
    @clients.bySocketId[socketId]?.forEach @bound 'cleanUpClient'

  parseServiceKey =(serviceKey)->
    last = null
    serviceInfo = serviceKey.split('.').reduce (acc, edge, i)-><commit_msg>authworker: add stub case for authenticating chat<commit_after>{EventEmitter} = require 'microemitter'

module.exports = class AuthWorker extends EventEmitter

  AuthedClient = require './authedclient'

  AUTH_EXCHANGE_OPTIONS =
    type        : 'fanout'
    autoDelete  : yes

  constructor:(@bongo, @resourceName, @presenceExchange='services-presence')->
    @services = {}
    @clients  = {
      bySocketId    : {}
      byExchange    : {}
      byRoutingKey  : {}
    }
    @counts   = {}

  bound: require 'koding-bound'

  authenticate:(messageData, routingKey, callback)->
    {clientId, channel, event} = messageData
    @requireSession clientId, routingKey, callback

  requireSession:(clientId, routingKey, callback)->
    {JSession} = @bongo.models
    JSession.fetchSession clientId, (err, session)=>
      if err? or not session? then @rejectClient routingKey
      else
        tokenHasChanged = session.clientId isnt clientId
        @updateSessionToken session.clientId, routingKey  if tokenHasChanged
        callback session

  updateSessionToken:(clientId, routingKey)->
    @bongo.respondToClient routingKey,
      method      : 'updateSessionToken'
      arguments   : [clientId]
      callbacks   : {}

  getNextServiceName:(serviceType)->
    count = @counts[serviceType] ?= 0
    servicesOfType = @services[serviceType]
    return  unless servicesOfType?.length
    serviceName = servicesOfType[count % servicesOfType.length]
    @counts[serviceType] += 1
    return serviceName

  addService:({serviceGenericName, serviceUniqueName})->
    servicesOfType = @services[serviceGenericName] ?= []
    servicesOfType.push serviceUniqueName 

  removeService:({serviceGenericName, serviceUniqueName})->
    servicesOfType = @services[serviceGenericName] 
    index = servicesOfType.indexOf serviceUniqueName
    servicesOfType.splice index, 1
    clientsByExchange = @clients.byExchange[serviceUniqueName]
    clientsByExchange?.forEach @bound 'cycleClient'

  cycleClient:(client)->
    {routingKey} = client
    @bongo.respondToClient routingKey, {
      method      : 'cycleChannel'
      arguments   : []
      callbacks   : {}
    }

  removeClient:(rest...)->
    if rest.length is 1
      [client] = rest
      return @removeClient client.socketId, client.exchange, client.routingKey
    [socketId, exchange, routingKey] = rest
    delete @clients.bySocketId[socketId]
    delete @clients.byExchange[exchange]
    delete @clients.byRoutingKey[routingKey]

  addClient:(socketId, exchange, routingKey, sendOk=yes)->
    if sendOk
      @bongo.respondToClient routingKey, {
        method    : 'authOk'
        arguments : []
        callbacks : {}
      }
    clientsBySocketId   = @clients.bySocketId[socketId]     ?= []
    clientsByExchange   = @clients.byExchange[exchange]     ?= []
    clientsByRoutingKey = @clients.byRoutingKey[routingKey] ?= []
    client = new AuthedClient {routingKey, socketId, exchange}
    clientsBySocketId.push client
    clientsByRoutingKey.push client
    clientsByExchange.push client

  rejectClient:(routingKey, message)->
    return console.trace()  unless routingKey?
    @bongo.respondToClient routingKey, {
      method    : 'error'
      arguments : [message: message ? 'Access denied']
      callbacks : {}
    }

  joinClient: do ->

    joinClientHelper =(messageData, routingKey, socketId)->
      @authenticate messageData, routingKey, (session)=>
        serviceResourceName = @getNextServiceName messageData.name
        unless serviceResourceName?
          @bongo.respondToClient routingKey, {
            method    : 'error'
            arguments : [message: 'Service unavailable!', code:503]
            callbacks : {}
          }
        else
          {connection} = @bongo.mq
          connection.exchange serviceResourceName, AUTH_EXCHANGE_OPTIONS,
            (exchange)=>
              exchange.publish 'auth.join', {
                username    : session.username
                routingKey  : routingKey
              }
              exchange.close() # don't leak a channel
              @addClient socketId, exchange.name, routingKey

    ensureGroupPermission =(group, account, roles, callback)->
      {JPermissionSet, JGroup} = @bongo.models
      client = {context: group.slug, connection: delegate: account}
      JPermissionSet.checkPermission client, "read activity", group,
        (err, hasPermission)->
          if err then callback err
          else unless hasPermission
            callback {message: 'Access denied!', code: 403}
          else
            JGroup.fetchSecretChannelName group.slug, callback

    joinClientGroupHelper =(messageData, routingKey, socketId)->
      {JAccount, JGroup} = @bongo.models
      fail = (err)=>
        console.error err  if err
        @rejectClient routingKey
      @authenticate messageData, routingKey, (session)=>
        unless session then fail()
        else JAccount.one {'profile.nickname': session.username},
          (err, account)=>
            if err or not account then fail err
            else JGroup.one {slug: messageData.group}, (err, group)=>
              if err or not group then fail err
              else 
                group.fetchRolesByAccount account, (err, roles)=>
                  if err or not roles then fail err
                  else
                    ensureGroupPermission.call this, group, account, roles,
                      (err, secretChannelName)=>
                        if err or not secretChannelName
                          @rejectClient routingKey
                        else
                          setSecretNameEvent = "#{routingKey}.setSecretName"
                          message = JSON.stringify secretChannelName
                          @bongo.respondToClient setSecretNameEvent, message

    joinClient =(messageData, socketId)->
      {channel, routingKey, serviceType} = messageData
      switch serviceType
        when 'bongo', 'kite'
          joinClientHelper.call this, messageData, routingKey, socketId
        when 'group'
          unless ///^group.#{messageData.group}///.test routingKey
            console.log 'rejecting', routingKey
            return @rejectClient routingKey
          joinClientGroupHelper.call this, messageData, routingKey, socketId
        when 'chat' then # TODO: handle authentication for chat
        when 'secret'
          @addClient socketId, routingKey, routingKey, no
        else
          @rejectClient routingKey

  cleanUpClient:(client)->
    @removeClient client
    @bongo.mq.connection.exchange client.exchange, AUTH_EXCHANGE_OPTIONS,
      (exchange)->
        exchange.publish 'auth.leave', {
          routingKey: client.routingKey
        }
        exchange.close() # don't leak a channel!

  cleanUpAfterDisconnect:(socketId)->
    @clients.bySocketId[socketId]?.forEach @bound 'cleanUpClient'

  parseServiceKey =(serviceKey)->
    last = null
    serviceInfo = serviceKey.split('.').reduce (acc, edge, i)->
{'fraction_non_alphanumeric': 0.09397711736345973, 'fraction_numerical': 0.001439159530833993, 'mean_word_length': 2.553566862694963, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6425500', 'n_tokens_mistral': 3835, 'n_tokens_neox': 3681, 'n_words': 889}