This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
adlin/token-validator/handler.go

202 lines
7.2 KiB
Go

package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/srs/adlin/libadlin"
)
var router = httprouter.New()
func Router() *httprouter.Router {
return router
}
type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params)) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
h := hmac.New(sha512.New, []byte(adlin.SharedSecret))
h.Write([]byte(fmt.Sprintf("%d", time.Now().Unix()/10)))
expectedMAC := h.Sum(nil)
h = hmac.New(sha512.New, []byte(adlin.SharedSecret))
h.Write([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1)))
previousMAC := h.Sum(nil)
if aauth, err := base64.StdEncoding.DecodeString(r.Header.Get("X-ADLIN-Authentication")); err != nil {
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusUnauthorized)
} else if !hmac.Equal(expectedMAC, aauth) && !hmac.Equal(previousMAC, aauth) {
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", "Bad authentication header"), http.StatusUnauthorized)
} else {
f(w, r, ps)
}
}
}
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
}
log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())
w.Header().Set("Content-Type", "application/json")
// Read Authorization header
var student *adlin.Student = nil
if cookie, err := r.Cookie("auth"); err == nil {
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
http.SetCookie(w, &http.Cookie{Name: "auth", Value: "", Path: baseURL + "/", Expires: time.Time{}, HttpOnly: true})
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable)
return
} else if session, err := adlin.GetSession(sessionid); err != nil {
http.SetCookie(w, &http.Cookie{Name: "auth", Value: "", Path: baseURL + "/", Expires: time.Time{}, HttpOnly: true})
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return
} else if session.IdStudent == nil {
student = nil
} else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
http.SetCookie(w, &http.Cookie{Name: "auth", Value: "", Path: baseURL + "/", Expires: time.Time{}, HttpOnly: true})
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return
} else {
student = std
}
}
// Check access limitation
for _, a := range access {
if err := a(student, r); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg":%q}`, err), http.StatusForbidden)
return
}
}
// Read the body
if r.ContentLength < 0 || r.ContentLength > 6553600 {
http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
return
}
var body []byte
if r.ContentLength > 0 {
tmp := make([]byte, 1024)
for {
n, err := r.Body.Read(tmp)
for j := 0; j < n; j++ {
body = append(body, tmp[j])
}
if err != nil || n <= 0 {
break
}
}
}
f(w, r, ps, body)
}
}
func responseHandler(f func(*http.Request, httprouter.Params, []byte) (interface{}, error)) func(http.ResponseWriter, *http.Request, httprouter.Params, []byte) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, body []byte) {
ret, err := f(r, ps, body)
// Format response
resStatus := http.StatusOK
if err != nil {
ret = map[string]string{"errmsg": err.Error()}
resStatus = http.StatusBadRequest
log.Println(r.RemoteAddr, resStatus, err.Error())
}
if ret == nil {
ret = map[string]string{"errmsg": "Page not found"}
resStatus = http.StatusNotFound
}
if str, found := ret.(string); found {
w.WriteHeader(resStatus)
io.WriteString(w, str)
w.Write([]byte("\n"))
} else if bts, found := ret.([]byte); found {
w.WriteHeader(resStatus)
w.Write(bts)
} else if j, err := json.Marshal(ret); err != nil {
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusInternalServerError)
} else {
w.WriteHeader(resStatus)
w.Write(j)
w.Write([]byte("\n"))
}
}
}
func challengeHandler(f func(*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, 0)
}
}
func definedChallengeHandler(f func(*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, chid)
}
}
func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) {
responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
return f(w, ps, b)
})(w, r, ps, b)
}, access...)
}
func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...)
}
func apiAuthHandler(f func(*adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
if cookie, err := r.Cookie("auth"); err != nil {
return nil, errors.New("Authorization required")
} else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
return nil, err
} else if session, err := adlin.GetSession(sessionid); err != nil {
return nil, err
} else if session.IdStudent == nil {
return nil, errors.New("Authorization required")
} else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
return nil, err
} else {
return f(std, ps, b)
}
}), access...)
}
func studentHandler(f func(*adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
return func(ps httprouter.Params, body []byte) (interface{}, error) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {
return nil, err
} else {
return f(student, body)
}
} else if student, err := adlin.GetStudent(sid); err != nil {
return nil, err
} else {
return f(student, body)
}
}
}