<commit_before>{Module} = require 'jraphical'

class PINExistsError extends Error
  constructor:(message)->
    return new PINExistsError(message) unless @ instanceof PINExistsError
    Error.call @
    @message = message
    @name = 'PINExistsError'

module.exports = class JVerificationToken extends Module

  {secure}    = require 'bongo'
  crypto      = require 'crypto'
  hat         = require 'hat'

  @share()

  @set
    sharedEvents  :
      static      : []
      instance    : []
    schema        :
      username    : String
      email       :
        type      : String
        email     : yes
      pin         : String
      createdAt   :
        type      : Date
        default   : -> new Date
      action      :
        type      : String
        enum      : [
          'invalid action type'
          # Add verification required action types here before use
          ['update-email','test-verify']
        ]

  @requestNewPin = (options, callback)->

    {action, email, subject, user, firstName} = options
    subject   or= "Here is your code"
    username    = user.getAt 'username'
    email     or= user.getAt 'email'
    firstName or= username

    if not email
      KodingError = require '../error'
      callback new KodingError "E-mail is not provided!"
    else
      @one {username, action}, (err, verify)=>
        if verify
          if Math.round((Date.now()-verify.createdAt)/60000) < 20
            callback if err then err else new PINExistsError "PIN exists and not expired, try again after 20 min for new PIN."
            return no

        # Remove all waiting pins for given action and email
        @remove {username, action}, (err, count)->
          if err then console.warn err
          else
            if count > 0 then console.log "#{count} expired PIN removed."
            else console.log "No such waiting PIN found."

            # Create a random pin
            plainPin = hat 16
            pin      = crypto.createHash('sha1').update(plainPin+'').digest('hex')

            # Create and send new pin
            confirmation = new JVerificationToken {username, action, email, pin}
            confirmation.save (err)->
              if err
                callback err
              else
                JMail = require './email'
                email = new JMail
                  from    : '<EMAIL>'
                  email   : email
                  subject : subject
                  content : confirmation.getTextBody firstName, plainPin
                  force   : yes

                email.save callback

  @confirmByPin = (options, callback)->

    {pin, email, action, username} = options

    # re-hash the pin
    pin = crypto.createHash('sha1').update(pin+'').digest('hex')

    @one {email, action, pin, username}, (err, verify)->
      if verify
        callback null, Math.round((Date.now()-verify.createdAt)/60000) < 20
        verify.remove()
      else
        callback err, false

  getTextBody:(firstName, plainPin)->
    """
    Hi #{firstName},

    You can use following code to complete your request:

      #{plainPin}

    --
    Koding Team

    """<commit_msg>Account: token invalidation is available for email change<commit_after>{Module} = require 'jraphical'

class PINExistsError extends Error
  constructor:(message)->
    return new PINExistsError(message) unless @ instanceof PINExistsError
    Error.call @
    @message = message
    @name = 'PINExistsError'

module.exports = class JVerificationToken extends Module

  {secure}    = require 'bongo'
  crypto      = require 'crypto'
  hat         = require 'hat'

  @share()

  @set
    sharedEvents  :
      static      : []
      instance    : []
    schema        :
      username    : String
      email       :
        type      : String
        email     : yes
      pin         : String
      createdAt   :
        type      : Date
        default   : -> new Date
      action      :
        type      : String
        enum      : [
          'invalid action type'
          # Add verification required action types here before use
          ['update-email','test-verify']
        ]

  @requestNewPin = (options, callback)->

    {action, email, subject, user, firstName} = options
    subject   or= "Here is your code"
    username    = user.getAt 'username'
    email     or= user.getAt 'email'
    firstName or= username

    if not email
      KodingError = require '../error'
      callback new KodingError "E-mail is not provided!"
    else
      @one {username, action}, (err, verify)=>
        if verify
          if Math.round((Date.now()-verify.createdAt)/60000) < 20
            callback if err then err else new PINExistsError "PIN exists and not expired, try again after 20 min for new PIN."
            return no

        # Remove all waiting pins for given action and email
        @remove {username, action}, (err, count)->
          if err then console.warn err
          else
            if count > 0 then console.log "#{count} expired PIN removed."
            else console.log "No such waiting PIN found."

            # Create a random pin
            plainPin = hat 16
            pin      = crypto.createHash('sha1').update(plainPin+'').digest('hex')

            # Create and send new pin
            confirmation = new JVerificationToken {username, action, email, pin}
            confirmation.save (err)->
              if err
                callback err
              else
                JMail = require './email'
                email = new JMail
                  from    : '<EMAIL>'
                  email   : email
                  subject : subject
                  content : confirmation.getTextBody firstName, plainPin
                  force   : yes

                email.save callback

  @confirmByPin = (options, callback)->

    {pin, email, action, username} = options

    # re-hash the pin
    pin = crypto.createHash('sha1').update(pin+'').digest('hex')

    @one {email, action, pin, username}, (err, verify)->
      if verify
        callback null, Math.round((Date.now()-verify.createdAt)/60000) < 20
        verify.remove()
      else
        callback err, false

  getTextBody:(firstName, plainPin)->
    """
    Hi #{firstName},

    You can use following code to complete your request:

      #{plainPin}

    --
    Koding Team

    """

  @invalidatePin = (options, callback)->
    {email, action, username} = options
    @one {email, action, username}, (err, verify)->
      return callback err  if err
      return callback new KodingError "token not found" unless verify
      verify.remove()
      callback null
{'fraction_non_alphanumeric': 0.09306184012066365, 'fraction_numerical': 0.006334841628959276, 'mean_word_length': 1.7733166039314094, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 27, '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': '1369208', 'n_tokens_mistral': 1795, 'n_tokens_neox': 1687, 'n_words': 583}