package server import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/json" "errors" "io" "local/dndex/config" "local/dndex/storage" "local/dndex/storage/entity" "net/http" "strings" "time" "github.com/google/uuid" ) func Auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { if !config.New().Auth { return nil } if err := auth(g, w, r); err != nil { json.NewEncoder(w).Encode(map[string]interface{}{"error": "error when authorizing: " + err.Error()}) return err } return nil } func auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { if isPublic(g, r) { return nil } if !hasAuth(r) { return requestAuth(g, w, r) } return checkAuth(g, w, r) } func isPublic(g storage.RateLimitedGraph, r *http.Request) bool { namespace, err := getAuthNamespace(r) if err != nil { return false } ones, err := g.List(r.Context(), namespace, UserKey) if err != nil { return false } if len(ones) == 0 { return false } return ones[0].Title == "" } func hasAuth(r *http.Request) bool { _, err := r.Cookie(AuthKey) return err == nil } func checkAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { namespace, err := getAuthNamespace(r) if err != nil { return err } token, _ := r.Cookie(AuthKey) results, err := g.List(r.Context(), namespace, token.Value) if err != nil { return err } if len(results) != 1 { return requestAuth(g, w, r) } modified := time.Unix(0, results[0].Modified) if time.Since(modified) > config.New().AuthLifetime { return requestAuth(g, w, r) } return nil } func requestAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { namespace, err := getAuthNamespace(r) if err != nil { http.Error(w, `{"error": "namespace required"}`, http.StatusBadRequest) return err } ones, err := g.List(r.Context(), namespace, UserKey) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return err } if len(ones) != 1 { http.NotFound(w, r) return errors.New("namespace not established") } userKey := ones[0] id := uuid.New().String() token := entity.One{ ID: id, Name: id, Title: namespace, } if err := g.Insert(r.Context(), namespace, token); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return err } encodedToken, err := aesEnc(userKey.Title, token.Name) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return err } http.SetCookie(w, &http.Cookie{Name: NewAuthKey, Value: encodedToken}) http.Redirect(w, r, r.URL.String(), http.StatusSeeOther) return errors.New("auth requested") } func aesEnc(key, payload string) (string, error) { if len(key) == 0 { return "", errors.New("key required") } key = strings.Repeat(key, 32)[:32] block, err := aes.NewCipher([]byte(key)) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err = io.ReadFull(rand.Reader, nonce); err != nil { return "", err } b := gcm.Seal(nonce, nonce, []byte(payload), nil) return base64.StdEncoding.EncodeToString(b), nil } func aesDec(key, payload string) (string, error) { if len(key) == 0 { return "", errors.New("key required") } key = strings.Repeat(key, 32)[:32] ciphertext, err := base64.StdEncoding.DecodeString(payload) if err != nil { return "", err } block, err := aes.NewCipher([]byte(key)) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } if len(ciphertext) < gcm.NonceSize() { return "", errors.New("short ciphertext") } b, err := gcm.Open(nil, ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():], nil) return string(b), err } func getAuthNamespace(r *http.Request) (string, error) { namespace, err := getNamespace(r) return strings.Join([]string{namespace, AuthKey}, "."), err } func getNamespace(r *http.Request) (string, error) { if strings.HasPrefix(r.URL.Path, config.New().FilePrefix) { path := strings.TrimPrefix(r.URL.Path, config.New().FilePrefix+"/") if path == r.URL.Path { return "", errors.New("no namespace on files") } path = strings.Split(path, "/")[0] if path == "" { return "", errors.New("empty namespace on files") } return path, nil } namespace := r.URL.Query().Get("namespace") if len(namespace) == 0 { return "", errors.New("no namespace found") } return namespace, nil }