nord vpnnord vpn
Ad

Untitled

mail@pastecode.io avatar
unknown
plain_text
18 days ago
6.4 kB
3
Indexable
Never
class JwtService
  attr_accessor :current_manage, :cache_key

  HS256 = 'HS256'
  JURNAL_CONSUMER_ID = ENV['MASTER_JURNAL_ID']

  def initialize(current_manage = nil, source='browser')
    @current_manage = current_manage
    @source = source

    if current_manage&.kong_id.present?
      @cache_key = jwt_cache_key(current_manage.kong_id)
    end
  end

  def generate_old_jwt_token(payload: {}, cached: true)
    jwt = Kong.create_jwt_credential(@current_manage.kong_id)
    payload.merge!({ :iss => jwt.key.to_s })

    JwtStruct.new({
      token: JWT.encode(payload, jwt_secret(jwt.secret.to_s), HS256),
      secret: jwt.secret,
      key: jwt.key,
      id: jwt.id
    })
  end

  def generate_jwt_token(payload: {}, cached: false)
    jwt = JwtStruct.new

    unless available?
      return jwt
    end

    if cached
      jwt = get_jwt_token
    end

    unless jwt_struct_empty?(jwt)
      return jwt
    end

    jwt = get_or_create_jwt_creds
    generated_at = DateTime.now.strftime('%Q') # epoch version
    payload.merge!({
      iss: "jwt.key.to_s",
      user_id: @current_manage.user_id,
      company_id: @current_manage.company_id,
      generated_at: generated_at
    })

    token = JWT.encode(payload, jwt_secret("jwt.secret.to_s"), HS256)
    jwt_result = {
      token: token,
      secret: "jwt.secret",
      key: "jwt.key",
      id: "jwt.id"
    }

    Rails.cache.write(@cache_key, jwt_result, expires_in: 2.hours)
    JwtStruct.new(jwt_result)
  end

  def switch_jwt_token(payload: {}, cached: false, prev_kong_id: '')
    unless available?
      return JwtStruct.new
    end

    prev_cache_key = jwt_cache_key(prev_kong_id)
    jwt = generate_jwt_token(payload: payload, cached: cached)
    prev_jwt_ttl = Rails.cache.data.ttl(prev_cache_key)

    Rails.cache.write(@cache_key, jwt, expires_in: prev_jwt_ttl.seconds)
    # delete prev token
    delete_jwt_token(prev_cache_key)
    jwt
  end

  def get_jwt_token
    unless available?
      return JwtStruct.new
    end

    jwt = Rails.cache.fetch(@cache_key) || {}

    unless jwt.present?
      return JwtStruct.new
    end

    JwtStruct.new(jwt)
  end

  def get_internal_jwt_token
    return JwtStruct.new unless @current_manage.present?
    jwt = Rails.cache.fetch("jwt_internal_token:#{@current_manage.kong_id}")

    unless jwt.present?
      return JwtStruct.new
    end

    JwtStruct.new(jwt)
  end

  def delete_jwt_token(cache_key=nil)
    unless available?
      return nil
    end

    if cache_key.nil?
      cache_key = @cache_key
    end

    Rails.cache.delete(cache_key)
  end

  def decode_jwt_token(token, secret=nil)
    unless available?
      return {}
    end

    if secret.nil?
      secret = get_or_create_jwt_creds&.secret || ''
    end

    JWT.decode(token, jwt_secret(secret), true, { algorithm: HS256 })
  end

  def fetch_or_create_internal_jwt_token
    return JwtStruct.new if @current_manage.nil?

    return JwtStruct.new if @current_manage.kong_id.nil? || @current_manage.kong_id.blank?

    internal_cache_key = "jwt_internal_token:#{@current_manage.kong_id}"
    cached_jwt = Rails.cache.fetch(internal_cache_key)
    return JwtStruct.new(cached_jwt) if cached_jwt.present?

    jwt_credentials = Kong.get_jwt_credential(@current_manage.kong_id)
    internal_jwt = {}
    payload = {}

    if jwt_credentials.blank?
      internal_jwt = Kong.create_jwt_credential(@current_manage.kong_id)
    else
      current_jwt = Rails.cache.fetch(@cache_key) || {}
      jwt_credentials.each do |jwt|
        next if current_jwt[:key] == jwt['key'] && current_jwt[:secret] == jwt['secret']

        internal_jwt = Kong::Jwt.new(
          {
            key: jwt["key"], secret: jwt["secret"], id: jwt["id"]
          }
        )
        break
      end

      if internal_jwt.blank?
        internal_jwt = Kong.create_jwt_credential(@current_manage.kong_id)
      end
    end

    generated_at = DateTime.now.strftime('%Q')
    payload.merge!({
      iss: internal_jwt.key.to_s,
      user_id: @current_manage.user_id,
      company_id: @current_manage.company_id,
      generated_at: generated_at
    })

    jwt_result = {
      token: JWT.encode(payload, jwt_secret(internal_jwt.secret.to_s), HS256),
      secret: internal_jwt.secret,
      key: internal_jwt.key,
      id: internal_jwt.id
    }

    Rails.cache.write(internal_cache_key, jwt_result, expires_in: 2.hours)

    JwtStruct.new(jwt_result)
  end

  private

  def get_or_create_jwt_creds(consumer_id=JURNAL_CONSUMER_ID)
    # get from cache
    jwt_cred = Rails.cache.fetch("jwt_cred:#{consumer_id}") || {}

    if jwt_cred.present?
      return jwt_cred
    end

    # get from kong
    jwt_creds = Kong.get_jwt_credential(consumer_id)

    if jwt_creds.present?
      cred = jwt_creds[0]
      jwt_cred = Kong::Jwt.new(
        {
          key: cred["key"], secret: cred["secret"], id: cred["id"]
        }
      )
    end

    # create from kong
    if jwt_cred.nil?
      jwt_cred = Kong.create_jwt_credential(consumer_id)
    end

    # set jwt creds to redis
    Rails.cache.write("jwt_cred:#{consumer_id}", jwt_cred, expires_in: 7.days)

    return jwt_cred
  end

  def jwt_struct_empty?(jwt)
    jwt == JwtStruct.new
  end

  def jwt_secret(secret)
    secret += '=' * (4 - secret.length.modulo(4))

    Base64.decode64(secret.tr('-_', '+/'))
  end

  def jwt_cache_key(kong_id)
    [ 'jwt_token', @source, kong_id ].join(':')
  end

  def available?
    unless FeatureActivator::FlipperEnabler.toggle_jwt_service?(@current_manage&.company)
      return false
    end

    unless @current_manage.present?
      return false
    end

    return true
  end

  class << self
    def valid_token_with_timestamp_and_expiration?(token, expiration = 5.minutes)
      begin
        decoded_token = JWT.decode(token, ENV['MASTER_JURNAL_ID'], true, { algorithm: 'HS256' })
        token = decoded_token[0]
        timestamp_str = token['timestamp']
        return false if timestamp_str.blank?

        timestamp = Time.zone.parse(timestamp_str)
        return false if timestamp.nil?

        Time.zone.now - timestamp < expiration
      rescue JWT::DecodeError => e
        return false
      end
    end

    def authenticate_jwt_header_by_timestamp(auth_header)
      return false if auth_header.blank?

      token = auth_header.delete_prefix('Bearer ')

      valid_token_with_timestamp_and_expiration?(token, 30.seconds)
    end
  end
end
Leave a Comment


nord vpnnord vpn
Ad