# frozen_string_literal: true require 'jwt' # This module implements JWT authentication module JwtAuthenticable # Module that adds jwt authentication methods to the client module Auth include Exceptions include Responses ALGORITHM = JwtAuthenticable.config.algorithm # Authenticates a user. # @raise MissingAuthScope if the jwt does not have the right scope def authenticate_user! validate_jwt_token! token: authorization_token! rescue MissingAuth, MissingAuthScope, InvalidAuthScheme, JWT::VerificationError, JWT::ExpiredSignature => e unauthorized(e.message) end # Consider any method below as private and not meant to be used by including classes # Validate that the JWT token signature and the following claims are valid: # - exp # - scope # @param token [String] JWT token string (just the token, with the header, payload and signature separated by '.') # @param is_researcher [Boolean] Whether to validate the token as a researcher's or a participant's # @raise AuthorizationError if the user is trying to login with the incorrect rights. # @return [Hash] the JWT payload def validate_jwt_token!(token:) # NOTE: it is still safe if JWT_SECRET_KEY is not set. The method will trigger a JWT exception JWT.decode(token, JwtAuthenticable.config.jwt_secret_key, true, { algorithm: ALGORITHM }).first end # Extracts the authorization token from the Authorization header # @note For now we only support Bearer schema with JWT # @raise [Exceptions::MissingAuth] Authorization header not present or empty # @raise [Exceptions::InvalidAuthScheme] Authorization scheme not understood or not supported # @return [String] the JWT token string def authorization_token! raise InvalidIncluder unless defined? request auth_token = request.headers['Authorization'] auth_token ||= request.cookies['bearer_token'] raise MissingAuth if auth_token.nil? || auth_token == '' raise InvalidAuthScheme if auth_token[0..6] != 'Bearer ' auth_token[7..] end end end