mentorenwahl/backend/src/backend/api/context.cr

210 lines
5.8 KiB
Crystal

# Mentorenwahl: A fullstack application for assigning mentors to students based on their whishes.
# Copyright (C) 2022 Dominic Grimm
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
require "graphql"
require "http/headers"
require "jwt/errors"
require "json"
module Backend
module Api
# GraphQL request context class
class Context < GraphQL::Context
# Development mode
getter development
# Request status
getter status
# Authenticated user
getter user
# User is admin
getter admin
# User's role
getter role
# User's external object
getter external
def initialize(
@development : Bool,
@status : Status,
@user : Db::User?,
@admin : Bool?,
@role : Schema::UserRole?,
@external : (Db::Teacher | Db::Student)?
)
end
def initialize(headers : HTTP::Headers, @development : Bool, @status = Status::OK, *rest)
super(*rest)
if (token = headers["authorization"]?) && token.starts_with?(Auth::BEARER)
begin
payload = Auth::Token.decode(token[Auth::BEARER.size..].strip)
rescue ex : JWT::ExpiredSignatureError
@status = Status::SessionExpired
rescue
@status = Status::JWTError
else
pp! payload
if payload.iss != "Mentorenwahl" || payload.vrs != Backend::VERSION
@status = Status::JWTError
elsif @user = Db::User.find(payload.context.user)
@admin = user.not_nil!.admin
@role = user.not_nil!.role.to_api
@external =
case @role.not_nil!
when .teacher?
@user.not_nil!.teacher
when .student?
@user.not_nil!.student
end
end
end
end
end
def on_development : Nil
{% if !flag?(:release) %}
if @development
yield
end
{% end %}
end
enum Status
OK
SessionExpired
JWTError
end
# User is authenticated
def authenticated? : Bool
!!@user && @status.ok?
end
# :ditto:
def authenticated! : Bool
# raise "Session expired" if @status.session_expired?
raise Errors::SessionExpired.new if @status.session_expired?
# raise "Not authenticated" unless authenticated?
raise Errors::NotAuthenticated.new unless authenticated?
true
end
# User is admin
def admin? : Bool
authenticated? && !!@admin
end
# :ditto:
def admin! : Bool
authenticated!
# raise "Invalid permissions" unless admin?
raise Errors::InvalidPermissions.new unless admin?
true
end
# User's is one of *roles*
def role?(roles : Array(Schema::UserRole), external_check = true) : Bool
return false unless authenticated?
roles.each do |role|
return true if @role == role &&
if external_check
role ==
case @external.not_nil!
when Db::Teacher
Schema::UserRole::Teacher
when Db::Student
Schema::UserRole::Student
end
else
true
end
end
false
end
# :ditto:
def role!(roles : Array(Schema::UserRole), external_check = true) : Bool
authenticated!
# raise "Invalid permissions" unless role?(roles, external_check)
raise Errors::InvalidPermissions.new unless role?(roles, external_check)
true
end
# User is teacher
def teacher?(external_check = true) : Bool
role?([Schema::UserRole::Teacher], external_check)
end
# :ditto:
def teacher!(external_check = true) : Bool
role!([Schema::UserRole::Teacher], external_check)
end
# User is student
def student?(external_check = true) : Bool
role?([Schema::UserRole::Student], external_check)
end
# :ditto:
def student!(external_check = true) : Bool
role!([Schema::UserRole::Student], external_check)
end
# Custom error handler
def handle_exception(ex : Exception) : String?
# pp! ex, ex.message, ex.class, typeof(ex), ex.is_a? Errors::PublicError
# # ex.message
case ex
when Errors::PublicError
ex.api_message
when Errors::PrivateError
{% if !flag?(:release) %}
if @development
ex.api_message
else
Errors::UNKNOWN_PRIVATE_ERROR
end
{% else %}
Errors::UNKNOWN_PRIVATE_ERROR
{% end %}
else
{% if !flag?(:release) %}
if @development
ex.message || Errors::UNKNOWN_PRIVATE_ERROR
else
Errors::UNKNOWN_PRIVATE_ERROR
end
{% else %}
Errors::UNKNOWN_PRIVATE_ERROR
{% end %}
end
end
end
end
end