theforeman/foreman

View on GitHub
app/services/jwt_token.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'jwt'
require 'base64'

class JwtToken < Struct.new(:token)
  class << self
    def encode(user, secret, expiration: nil, scope: [])
      payload = prepare_payload(user, secret, expiration: expiration, scope: scope)
      new JWT.encode(payload, secret)
    end

    private

    def prepare_payload(user, secret, expiration: nil, scope: [])
      jti_raw = [secret, iat].join(':')
      jti = Digest::SHA256.hexdigest(jti_raw)
      payload = {
        user_id: user.id,
        iat: iat,
        jti: jti,
      }

      payload[:exp] = (Time.now.to_i + expiration) if expiration
      payload[:scope] = format_scope(scope) if scope.any?
      payload
    end

    def iat
      Time.now.to_i
    end

    # Format scope to string 'controller#action1 controller#action2'
    def format_scope(scope)
      scope.map { |sc| sc[:actions].map { |action| "#{sc[:controller]}##{action}" }.join(' ') }.join(' ')
    end
  end

  def decode
    return nil if token.blank?
    return nil if secret.blank?

    payload = JWT.decode(token, secret.token)
    payload.first
  end

  def to_s
    token
  end

  # This method does not verify if the token signature is valid
  # defined? keyword checks if expression is currently defined. It
  # therefore, helps to memoize the nil JWT values.
  def decoded_payload
    return @decoded_payload if defined?(@decoded_payload)
    @decoded_payload = JWT.decode(token, nil, false).first

    unless @decoded_payload.is_a?(Hash)
      logger.error "Invalid decoded JWT format."
      logger.debug "Received payload: #{@decoded_payload}"
      @decoded_payload = nil
    end
    @decoded_payload
  rescue JWT::DecodeError => e
    Foreman::Logging.exception('Failed to decode JWT', e)
    @decoded_payload = nil
  end

  private

  def secret
    return @secret if defined? @secret
    @secret = JwtSecret.find_by(user: user_id)
  end

  def user_id
    @user_id ||= decoded_payload.try(:[], 'user_id')
  end
end