Browse Source

Simplify mailbox handling

- Make http API more RESTful
- Migrate to numerical mailbox identificator in JS
- Make mailbox navigation fully path-based
- Refactor server side handling, make it more straignt forward
  and clear
- Fix security hall in attachments handling
Alexey Edelev 4 years ago
parent
commit
813cf48034
9 changed files with 301 additions and 215 deletions
  1. 6 0
      db/db.go
  2. 0 9
      utils/regexp.go
  3. 162 0
      web/auth.go
  4. 31 17
      web/js/index.js
  5. 4 5
      web/mail.go
  6. 22 12
      web/mailbox.go
  7. 8 10
      web/securezone.go
  8. 67 161
      web/server.go
  9. 1 1
      web/templates/statusline.html

+ 6 - 0
db/db.go

@@ -621,3 +621,9 @@ func (s *Storage) cleanupAttachments(user, email string) error {
 
 	return nil
 }
+
+func (s *Storage) CheckAttachment(user, attachment string) bool {
+	mailsCollection := s.db.Collection(qualifiedMailCollection(user))
+	result := mailsCollection.FindOne(context.Background(), bson.M{"mail.body.attachments.id": attachment})
+	return result.Err() == nil
+}

+ 0 - 9
utils/regexp.go

@@ -38,7 +38,6 @@ const (
 	BoundaryStartRegExp = "^--(.*)"
 	BoundaryEndRegExp   = "^--(.*)--$"
 	BoundaryRegExp      = "boundary=\"(.*)\""
-	MailboxRegExp       = "^/m(\\d+)/?(.*)"
 	FullNameRegExp      = "^[\\w]+[\\w ]*$"
 	EncodedStringRegExp = "=\\?.+\\?="
 )
@@ -73,7 +72,6 @@ type regExpUtils struct {
 	BoundaryStartFinder *regexp.Regexp
 	BoundaryEndFinder   *regexp.Regexp
 	BoundaryFinder      *regexp.Regexp
-	MailboxFinder       *regexp.Regexp
 	FullNameChecker     *regexp.Regexp
 	EncodedStringFinder *regexp.Regexp
 }
@@ -127,12 +125,6 @@ func newRegExpUtils() (*regExpUtils, error) {
 		return nil, err
 	}
 
-	mailboxFinder, err := regexp.Compile(MailboxRegExp)
-	if err != nil {
-		log.Fatalf("Invalid regexp %s\n", err)
-		return nil, err
-	}
-
 	fullNameChecker, err := regexp.Compile(FullNameRegExp)
 	if err != nil {
 		log.Fatalf("Invalid regexp %s\n", err)
@@ -154,7 +146,6 @@ func newRegExpUtils() (*regExpUtils, error) {
 		BoundaryEndFinder:   boundaryEndFinder,
 		BoundaryFinder:      boundaryFinder,
 		DomainChecker:       domainChecker,
-		MailboxFinder:       mailboxFinder,
 		FullNameChecker:     fullNameChecker,
 		EncodedStringFinder: encodedString,
 	}

+ 162 - 0
web/auth.go

@@ -0,0 +1,162 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of gostfix project https://git.semlanik.org/semlanik/gostfix
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package web
+
+import (
+	"fmt"
+	template "html/template"
+	"log"
+	"net/http"
+
+	"git.semlanik.org/semlanik/gostfix/common"
+	"git.semlanik.org/semlanik/gostfix/config"
+	"git.semlanik.org/semlanik/gostfix/utils"
+)
+
+func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
+	// if session, err := s.sessionStore.Get(r, CookieSessionToken); err == nil && session.Values["user"] != nil && session.Values["token"] != nil {
+	// 	http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+	// 	return
+	// }
+	if !config.ConfigInstance().RegistrationEnabled {
+		s.error(http.StatusNotImplemented, "Registration is disabled on this server", w)
+		return
+	}
+
+	//Check if user already logged in and entered register page accidently
+	if s.authenticator.Verify(s.extractAuth(w, r)) {
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
+		return
+	}
+
+	switch r.Method {
+	case "GET":
+		fmt.Fprint(w, s.templater.ExecuteRegister(&struct {
+			Version string
+			Domain  string
+		}{common.Version, config.ConfigInstance().MyDomain}))
+		return
+	case "POST":
+		user := r.FormValue("user")
+		password := r.FormValue("password")
+		fullName := r.FormValue("fullName")
+		if user != "" && password != "" && fullName != "" {
+			ok, email := s.checkEmail(user)
+			if ok && len(password) < 128 && len(fullName) < 128 && utils.RegExpUtilsInstance().FullNameChecker.MatchString(fullName) {
+				err := s.storage.AddUser(email, password, fullName)
+				if err != nil {
+					log.Println(err.Error())
+					s.error(http.StatusInternalServerError, "Unable to create user", w)
+					return
+				}
+
+				s.scanner.Reconfigure()
+				token, _ := s.authenticator.Login(email, password)
+				s.login(email, token, w, r)
+				return
+			}
+		}
+	}
+
+	s.error(http.StatusNotImplemented, "Invalid registration handling", w)
+}
+
+func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
+	switch r.Method {
+	case "GET":
+		//Check if user already logged in and entered login page accidently
+		if s.authenticator.Verify(s.extractAuth(w, r)) {
+			http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
+			return
+		}
+
+		var signupTemplate template.HTML
+		if config.ConfigInstance().RegistrationEnabled {
+			signupTemplate = template.HTML(s.templater.ExecuteSignup(""))
+		} else {
+			signupTemplate = ""
+		}
+
+		//Otherwise make sure user logged out and show login page
+		s.logout(w, r)
+		fmt.Fprint(w, s.templater.ExecuteLogin(&struct {
+			Version string
+			Signup  template.HTML
+		}{common.Version, signupTemplate}))
+	case "POST":
+		//Check passed in form login/password pair first
+		user := r.FormValue("user")
+		password := r.FormValue("password")
+		token, ok := s.authenticator.Login(user, password)
+		if ok {
+			s.login(user, token, w, r)
+			return
+		}
+	}
+}
+
+func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
+	s.logout(w, r)
+	http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
+}
+
+func (s *Server) handleCheckEmail(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseForm(); err == nil {
+		if ok, _ := s.checkEmail(r.FormValue("user")); ok {
+			w.Write([]byte{0})
+			return
+		}
+		s.error(http.StatusNotAcceptable, "Email exists", w)
+		return
+	}
+	s.error(http.StatusBadRequest, "Invalid arguments", w)
+	return
+}
+
+func (s *Server) checkEmail(user string) (bool, string) {
+	email := user + "@" + config.ConfigInstance().MyDomain
+	return utils.RegExpUtilsInstance().EmailChecker.MatchString(email) && !s.storage.CheckEmailExists(email), email
+}
+
+func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
+	session, err := s.sessionStore.Get(r, CookieSessionToken)
+	if err == nil {
+		if session.Values["user"] != nil && session.Values["token"] != nil {
+			s.authenticator.Logout(session.Values["user"].(string), session.Values["token"].(string))
+		}
+		session.Values["user"] = ""
+		session.Values["token"] = ""
+		session.Save(r, w)
+	}
+}
+
+func (s *Server) login(user, token string, w http.ResponseWriter, r *http.Request) {
+	session, _ := s.sessionStore.Get(r, CookieSessionToken)
+	session.Values["user"] = user
+	session.Values["token"] = token
+	session.Save(r, w)
+	http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
+}

+ 31 - 17
web/js/index.js

@@ -26,9 +26,8 @@
 var currentFolder = '';
 var currentPage = 0;
 var currentMail = '';
-var mailbox = '';
+var mailbox = null;
 var pageMax = 10;
-const mailboxRegex = /^(\/m\d+)/g;
 const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 const emailEndRegex = /[;,\s]/g;
 
@@ -59,11 +58,11 @@ $(document).ready(function(){
         type: 'POST'
     });
 
-    urlPaths = mailboxRegex.exec($(location).attr('pathname'));
-    if (urlPaths != null && urlPaths.length === 2) {
-        mailbox = urlPaths[0];
+    urlPaths = $(location).attr('pathname').split('/');
+    if (urlPaths != null && urlPaths.length >= 2 && urlPaths[1] == 'm') {
+        mailbox = urlPaths[2];
     } else {
-        mailbox = '';
+        mailbox = null;
     }
 
     $(window).bind('hashchange', onHashChanged);
@@ -219,13 +218,12 @@ function requestMail(mailId) {
 }
 
 function loadFolders() {
-    if (mailbox == '') {
-        $('#folders').html('Unable to load folder list');
-        return;
+    if (mailbox === null) {
+        return
     }
 
     $.ajax({
-        url: mailbox + '/folders',
+        url: '/m/' + mailbox + '/folders',
         success: function(result) {
             folderList = jQuery.parseJSON(result);
             for(var i = 0; i < folderList.folders.length; i++) {
@@ -241,8 +239,12 @@ function loadFolders() {
 }
 
 function folderStat(folder) {
+    if (mailbox === null) {
+        return
+    }
+
     $.ajax({
-        url: mailbox + '/folderStat',
+        url: '/m/' + mailbox + '/folderStat',
         data: {
             folder: folder
         },
@@ -271,8 +273,12 @@ function closeMailNew() {
 }
 
 function loadStatusLine() {
+    if (mailbox === null) {
+        return
+    }
+
     $.ajax({
-        url: mailbox + '/statusLine',
+        url: '/m/' + mailbox + '/statusLine',
         success: function(result) {
             $('#statusLine').html(result);
         },
@@ -445,7 +451,11 @@ function setMailNewVisible(visible) {
 }
 
 function updateMailList(folder, page) {
-    if (mailbox == '' || folder == '') {
+    if (mailbox === null) {
+        return
+    }
+
+    if (folder == '') {
         if ($('#mailList')) {
             $('#mailList').html('Unable to load message list');
         }
@@ -453,7 +463,7 @@ function updateMailList(folder, page) {
     }
 
     $.ajax({
-        url: mailbox + '/mailList',
+        url: '/m/' + mailbox + '/mailList',
         data: {
             folder: folder,
             page: page
@@ -500,6 +510,10 @@ function toggleDropDown(dd) {
 }
 
 function sendNewMail(force) {
+    if (mailbox === null) {
+        return
+    }
+
     if (toEmailList.length <= 0) {
         return;
     }
@@ -517,7 +531,7 @@ function sendNewMail(force) {
     $('#newMailTo').val(composedEmailString);
     var formValue = $('#mailNewForm').serialize();
     $.ajax({
-        url: mailbox + '/sendNewMail',
+        url: '/m/' + mailbox + '/sendNewMail',
         data: formValue,
         success: function() {
             $('#newMailEditor').val('');
@@ -549,7 +563,7 @@ function connectNotifier() {
     if (window.location.protocol  !== 'https:') {
         protocol = 'ws://';
     }
-    notifierSocket = new WebSocket(protocol + window.location.host + mailbox + '/notifierSubscribe');
+    notifierSocket = new WebSocket(protocol + window.location.host + '/m/' + mailbox + '/notifierSubscribe');
     notifierSocket.onmessage = function (e) {
         for (var i = 0; i < folders.length; i++) {
             folderStat(folders[i]);
@@ -664,4 +678,4 @@ function enableRestoreFunctionality() {
         $('#multiActionsRestore').css('display', 'none');
         $('[id^="restoreListIcon"]').css('display', 'none');
     }
-}
+}

+ 4 - 5
web/mail.go

@@ -36,11 +36,10 @@ import (
 	"git.semlanik.org/semlanik/gostfix/utils"
 )
 
-func (s *Server) handleMailRequest(w http.ResponseWriter, r *http.Request, mailId string) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
-		return
+func (s *Server) handleMailRequest(w http.ResponseWriter, r *http.Request, user, mailId string) {
+	if user == "" {
+		log.Printf("User could not be empty. Invalid usage of handleMailRequest")
+		panic(nil)
 	}
 
 	if mailId == "" {

+ 22 - 12
web/mailbox.go

@@ -57,8 +57,12 @@ func (s *Server) handleMailbox(w http.ResponseWriter, user, email string) {
 	}))
 }
 
-func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.ResponseWriter, r *http.Request) {
-	log.Printf("Handle mailbox %s", path)
+func (s *Server) handleMailboxRequest(w http.ResponseWriter, r *http.Request, user string, urlParts []string) {
+	if user == "" {
+		log.Printf("User could not be empty. Invalid usage of handleMailboxRequest")
+		panic(nil)
+	}
+
 	emails, err := s.storage.GetEmails(user)
 
 	if err != nil || len(emails) <= 0 {
@@ -66,18 +70,25 @@ func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.Res
 		return
 	}
 
-	if len(emails) <= mailbox {
-		if path == "" {
-			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-		} else {
-			s.error(http.StatusInternalServerError, "Unable to access mailbox", w)
-		}
+	if len(urlParts) < 2 {
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
+		return
+	}
+
+	mailbox, err := strconv.Atoi(urlParts[1])
+	if err != nil || mailbox < 0 || len(emails) <= mailbox {
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
 		return
 	}
 
-	switch path {
-	case "":
+	if len(urlParts) < 3 {
 		s.handleMailbox(w, user, emails[mailbox])
+		return
+	}
+
+	log.Printf("Handle mailbox function %s", urlParts[2])
+
+	switch urlParts[2] {
 	case "folders":
 		s.handleFolders(w, user, emails[mailbox])
 	case "folderStat":
@@ -91,7 +102,7 @@ func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.Res
 	case "notifierSubscribe":
 		s.Notifier.handleNotifierRequest(w, r, emails[mailbox])
 	default:
-		http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
 	}
 }
 
@@ -233,7 +244,6 @@ func (s *Server) extractFolder(email string, r *http.Request) string {
 }
 
 func (s *Server) handleNewMail(w http.ResponseWriter, r *http.Request, user, email string) {
-
 	rawMail := &common.Mail{
 		Header: &common.MailHeader{
 			From:    email,

+ 8 - 10
web/securezone.go

@@ -33,20 +33,18 @@ import (
 	"git.semlanik.org/semlanik/gostfix/common"
 )
 
-func (s *Server) handleSecureZone(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
-		return
+func (s *Server) handleSecureZone(w http.ResponseWriter, r *http.Request, user string) {
+	if user == "" {
+		log.Printf("User could not be empty. Invalid usage of handleMailRequest")
+		panic(nil)
 	}
 	s.error(http.StatusNotImplemented, "Admin panel is not implemented", w)
 }
 
-func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
-	user, token := s.extractAuth(w, r)
-	if !s.authenticator.Verify(user, token) {
-		s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
-		return
+func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request, user string) {
+	if user == "" {
+		log.Printf("User could not be empty. Invalid usage of handleMailRequest")
+		panic(nil)
 	}
 
 	switch r.Method {

+ 67 - 161
web/server.go

@@ -27,17 +27,14 @@ package web
 
 import (
 	"fmt"
-	"html/template"
 	"log"
 	"net/http"
-	"strconv"
 	"strings"
 
 	auth "git.semlanik.org/semlanik/gostfix/auth"
 	common "git.semlanik.org/semlanik/gostfix/common"
 	"git.semlanik.org/semlanik/gostfix/config"
 	db "git.semlanik.org/semlanik/gostfix/db"
-	utils "git.semlanik.org/semlanik/gostfix/utils"
 
 	sessions "github.com/gorilla/sessions"
 )
@@ -105,176 +102,97 @@ func (s *Server) Run() {
 }
 
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	fmt.Println(r.URL.Path)
-	fmt.Println(r.Method)
-	if utils.StartsWith(r.URL.Path, "/css/") ||
-		utils.StartsWith(r.URL.Path, "/assets/") ||
-		utils.StartsWith(r.URL.Path, "/js/") {
-		s.fileServer.ServeHTTP(w, r)
-	} else if utils.StartsWith(r.URL.Path, "/attachment") {
-		s.attachmentsServer.ServeHTTP(w, r)
-	} else if cap := utils.RegExpUtilsInstance().MailboxFinder.FindStringSubmatch(r.URL.Path); len(cap) == 3 {
-		user, token := s.extractAuth(w, r)
-		if !s.authenticator.Verify(user, token) {
-			s.logout(w, r)
-			http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
-			return
-		}
-
-		mailbox, err := strconv.Atoi(cap[1])
-		if err != nil || mailbox < 0 {
-			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-			return
-		}
-
-		path := cap[2]
-
-		s.handleMailboxRequest(path, user, mailbox, w, r)
-	} else {
-		urlParts := strings.Split(r.URL.Path, "/")
-		if len(urlParts) < 2 {
-			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-			return
-		}
+	log.Printf("%s %s", r.Method, r.URL.Path)
+	urlParts := strings.Split(r.URL.Path, "/")[1:]
+	if len(urlParts) == 0 || urlParts[0] == "" {
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
+		return
+	}
 
-		fmt.Println("urlParts:" + urlParts[1])
-		switch urlParts[1] {
-		case "login":
-			s.handleLogin(w, r)
-		case "logout":
-			s.handleLogout(w, r)
-		case "register":
-			s.handleRegister(w, r)
-		case "checkEmail":
-			s.handleCheckEmail(w, r)
-		case "mail":
-			if len(urlParts) == 3 {
-				s.handleMailRequest(w, r, urlParts[2])
-			} else {
-				//TODO: return mail list here
-			}
-		case "settings":
-			s.handleSettings(w, r)
-		case "admin":
-			s.handleSecureZone(w, r)
-		default:
-			http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-		}
+	switch urlParts[0] {
+	case "css":
+		fallthrough
+	case "assets":
+		fallthrough
+	case "js":
+		s.fileServer.ServeHTTP(w, r)
+	case "login":
+		s.handleLogin(w, r)
+	case "logout":
+		s.handleLogout(w, r)
+	case "register":
+		s.handleRegister(w, r)
+	case "checkEmail":
+		s.handleCheckEmail(w, r)
+	default:
+		s.handleSecure(w, r, urlParts)
 	}
 }
 
-func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
-	// if session, err := s.sessionStore.Get(r, CookieSessionToken); err == nil && session.Values["user"] != nil && session.Values["token"] != nil {
-	// 	http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-	// 	return
-	// }
-	if !config.ConfigInstance().RegistrationEnabled {
-		s.error(http.StatusNotImplemented, "Registration is disabled on this server", w)
+func (s *Server) handleSecure(w http.ResponseWriter, r *http.Request, urlParts []string) {
+	user, token := s.extractAuth(w, r)
+	if !s.authenticator.Verify(user, token) {
+		if r.Method == "GET" && urlParts[0] == "m" {
+			http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
+		} else {
+			s.error(http.StatusUnauthorized, "You are not allowed to access this function", w)
+		}
 		return
 	}
 
-	if err := r.ParseForm(); err == nil {
-		user := r.FormValue("user")
-		password := r.FormValue("password")
-		fullName := r.FormValue("fullName")
-		if user != "" && password != "" && fullName != "" {
-			ok, email := s.checkEmail(user)
-			if ok && len(password) < 128 && len(fullName) < 128 && utils.RegExpUtilsInstance().FullNameChecker.MatchString(fullName) {
-				err := s.storage.AddUser(email, password, fullName)
-				if err != nil {
-					log.Println(err.Error())
-					s.error(http.StatusInternalServerError, "Unable to create user", w)
-					return
-				}
-
-				s.scanner.Reconfigure()
-				token, _ := s.authenticator.Login(email, password)
-				s.login(email, token, w, r)
-				return
-			}
+	switch urlParts[0] {
+	case "m":
+		s.handleMailboxRequest(w, r, user, urlParts)
+	case "attachment":
+		if len(urlParts) == 2 {
+			s.handleAttachment(w, r, user, urlParts[1])
+		} else {
+			s.error(http.StatusBadRequest, "Invalid attachments request", w)
+		}
+	case "mail":
+		if len(urlParts) == 2 {
+			s.handleMailRequest(w, r, user, urlParts[1])
+		} else {
+			//TODO: return mail list here
 		}
+	case "settings":
+		s.handleSettings(w, r, user)
+	case "admin":
+		s.handleSecureZone(w, r, user)
+	default:
+		http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
 	}
-
-	fmt.Fprint(w, s.templater.ExecuteRegister(&struct {
-		Version string
-		Domain  string
-	}{common.Version, config.ConfigInstance().MyDomain}))
 }
 
-func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
-	//Check passed in form login/password pair first
-	if err := r.ParseForm(); err == nil {
-		user := r.FormValue("user")
-		password := r.FormValue("password")
-		token, ok := s.authenticator.Login(user, password)
-		if ok {
-			s.login(user, token, w, r)
-			return
-		}
+func (s *Server) handleAttachment(w http.ResponseWriter, r *http.Request, user, attachment string) {
+	if user == "" {
+		log.Printf("User could not be empty. Invalid usage of handleMailRequest")
+		panic(nil)
 	}
 
-	//Check if user already logged in and entered login page accidently
-	if s.authenticator.Verify(s.extractAuth(w, r)) {
-		http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+	if r.Method != "GET" {
+		s.error(http.StatusNotImplemented, "You only may download attachments", w)
 		return
 	}
 
-	var signupTemplate template.HTML
-	if config.ConfigInstance().RegistrationEnabled {
-		signupTemplate = template.HTML(s.templater.ExecuteSignup(""))
-	} else {
-		signupTemplate = ""
-	}
-
-	//Otherwise make sure user logged out and show login page
-	s.logout(w, r)
-	fmt.Fprint(w, s.templater.ExecuteLogin(&struct {
-		Version string
-		Signup  template.HTML
-	}{common.Version, signupTemplate}))
-}
-
-func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
-	s.logout(w, r)
-	http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
-}
-
-func (s *Server) handleCheckEmail(w http.ResponseWriter, r *http.Request) {
-	if err := r.ParseForm(); err == nil {
-		if ok, _ := s.checkEmail(r.FormValue("user")); ok {
-			w.Write([]byte{0})
-			return
-		}
-		s.error(http.StatusNotAcceptable, "Email exists", w)
+	if !s.storage.CheckAttachment(user, attachment) {
+		s.error(http.StatusNotFound, "Attachment not found", w)
 		return
 	}
-	s.error(http.StatusBadRequest, "Invalid arguments", w)
-	return
-}
 
-func (s *Server) checkEmail(user string) (bool, string) {
-	email := user + "@" + config.ConfigInstance().MyDomain
-	return utils.RegExpUtilsInstance().EmailChecker.MatchString(email) && !s.storage.CheckEmailExists(email), email
+	s.attachmentsServer.ServeHTTP(w, r)
 }
 
-func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
+func (s *Server) extractAuth(w http.ResponseWriter, r *http.Request) (user, token string) {
 	session, err := s.sessionStore.Get(r, CookieSessionToken)
-	if err == nil {
-		if session.Values["user"] != nil && session.Values["token"] != nil {
-			s.authenticator.Logout(session.Values["user"].(string), session.Values["token"].(string))
-		}
-		session.Values["user"] = ""
-		session.Values["token"] = ""
-		session.Save(r, w)
+	if err != nil {
+		log.Printf("Unable to read user session %s\n", err)
+		return
 	}
-}
+	user, _ = session.Values["user"].(string)
+	token, _ = session.Values["token"].(string)
 
-func (s *Server) login(user, token string, w http.ResponseWriter, r *http.Request) {
-	session, _ := s.sessionStore.Get(r, CookieSessionToken)
-	session.Values["user"] = user
-	session.Values["token"] = token
-	session.Save(r, w)
-	http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
+	return
 }
 
 func (s *Server) error(code int, text string, w http.ResponseWriter) {
@@ -289,15 +207,3 @@ func (s *Server) error(code int, text string, w http.ResponseWriter) {
 		Version: common.Version,
 	}))
 }
-
-func (s *Server) extractAuth(w http.ResponseWriter, r *http.Request) (user, token string) {
-	session, err := s.sessionStore.Get(r, CookieSessionToken)
-	if err != nil {
-		log.Printf("Unable to read user session %s\n", err)
-		return
-	}
-	user, _ = session.Values["user"].(string)
-	token, _ = session.Values["token"].(string)
-
-	return
-}

+ 1 - 1
web/templates/statusline.html

@@ -9,7 +9,7 @@
     </div>
     <div id="emailSelector" class="dropdown-content">
         {{range .EmailsIndexes}}
-        <a href="/m{{.Index}}">{{.Email}}</a>
+        <a href="/m/{{.Index}}">{{.Email}}</a>
         {{end}}
     </div>
     <div class="noselect" style="position: relative;">