# 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 . 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