...

Source file src/codeberg.org/tslocum/sriracha/internal/server/server_subscribe.go

Documentation: codeberg.org/tslocum/sriracha/internal/server

     1  package server
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"codeberg.org/tslocum/sriracha/internal/database"
    11  	. "codeberg.org/tslocum/sriracha/model"
    12  	. "codeberg.org/tslocum/sriracha/util"
    13  	"github.com/leonelquinteros/gotext"
    14  )
    15  
    16  func (s *Server) subscriptionConfirmKey(sub *Subscription) string {
    17  	return md5Sum(s.hashData(md5Sum(fmt.Sprintf("%s/%d", sub.Email, sub.Confirm))))
    18  }
    19  
    20  func (s *Server) serveSubscribe(db *database.DB, w http.ResponseWriter, r *http.Request) {
    21  	data := s.buildData(db, w, r)
    22  	data.Boards = db.AllBoards()
    23  	if !s.opt.Notifications {
    24  		data.BoardError(w, "Email notifications are disabled.")
    25  		return
    26  	}
    27  	data.Template = "subscribe"
    28  
    29  	key := r.URL.Query().Get("key")
    30  	if key != "" {
    31  		email := r.URL.Query().Get("email")
    32  		if email == "" {
    33  			data.BoardError(w, "Invalid email.")
    34  			return
    35  		}
    36  		expectedKey := md5Sum(s.hashData(md5Sum(email)))
    37  		if key != expectedKey {
    38  			data.BoardError(w, "Invalid access key.")
    39  			return
    40  		}
    41  		data.Extra = email
    42  		data.Extra2 = key
    43  
    44  		var confirmed bool
    45  		subs := db.SubscriptionsByEmail(email)
    46  		for _, sub := range subs {
    47  			if sub.Confirm == 0 {
    48  				confirmed = true
    49  				break
    50  			}
    51  		}
    52  		if !confirmed {
    53  			if len(subs) == 0 {
    54  				data.BoardError(w, "Your email address is unconfirmed. Subscribe to request a confirmation link.")
    55  				return
    56  			}
    57  			const errorMessage = "Click the confirmation link emailed to you."
    58  			confirmKey := r.URL.Query().Get("confirm")
    59  			if confirmKey == "" {
    60  				data.BoardError(w, "Your email address is unconfirmed. "+errorMessage)
    61  				return
    62  			}
    63  			expectedConfirmKey := s.subscriptionConfirmKey(subs[0])
    64  			if confirmKey != expectedConfirmKey {
    65  				data.BoardError(w, "Invalid confirmation key. "+errorMessage)
    66  				return
    67  			}
    68  			subs[0].Confirm = 0
    69  			subs[0].IP = ""
    70  			db.UpdateSubscription(subs[0])
    71  
    72  			data.Info = "Subscription confirmed."
    73  		}
    74  
    75  		if r.Method == http.MethodPost {
    76  			for _, sub := range subs {
    77  				v := FormNegInt(r, fmt.Sprintf("sub%d", sub.ID))
    78  
    79  				// Board subscription.
    80  				if sub.Board != 0 {
    81  					switch v {
    82  					case int(SubscriptionThreads), int(SubscriptionAll):
    83  						sub.Target = v
    84  						db.UpdateSubscription(sub)
    85  					case 1:
    86  						db.DeleteSubscription(sub)
    87  					}
    88  					continue
    89  				}
    90  
    91  				// Post subscription.
    92  				if v == 1 {
    93  					db.DeleteSubscription(sub)
    94  				}
    95  			}
    96  
    97  			subs = db.SubscriptionsByEmail(email)
    98  		}
    99  
   100  		boardLabels := make(map[int]string)
   101  		for _, board := range data.Boards {
   102  			boardLabels[board.ID] = board.Path()
   103  		}
   104  
   105  		sort.Slice(subs, func(i, j int) bool {
   106  			iBoard, jBoard := subs[i].Board != 0, subs[j].Board != 0
   107  			if iBoard != jBoard {
   108  				return iBoard
   109  			} else if iBoard && jBoard {
   110  				return boardLabels[subs[i].Board] < boardLabels[subs[j].Board]
   111  			}
   112  			return subs[i].Target < subs[j].Target
   113  		})
   114  		data.Subscriptions = subs
   115  
   116  		data.execute(w)
   117  		return
   118  	}
   119  
   120  	boardID := PathInt(r, "/sriracha/subscribe/board/")
   121  	if boardID > 0 {
   122  		board := db.BoardByID(boardID)
   123  		if board == nil {
   124  			data.BoardError(w, "Invalid board.")
   125  			return
   126  		}
   127  		data.Board = board
   128  	} else {
   129  		postID := PathInt(r, "/sriracha/subscribe/post/")
   130  		if postID > 0 {
   131  			post := db.PostByID(postID)
   132  			if post == nil {
   133  				data.BoardError(w, "Invalid post.")
   134  				return
   135  			}
   136  			data.Board = post.Board
   137  			data.Post = post
   138  		}
   139  	}
   140  
   141  	if data.Post == nil && data.Board == nil {
   142  		http.Redirect(w, r, "/sriracha/", http.StatusFound)
   143  		return
   144  	}
   145  
   146  	if r.Method == http.MethodPost {
   147  		email := FormString(r, "email")
   148  		if email == "" {
   149  			data.BoardError(w, "Enter your email address to subscribe.")
   150  			return
   151  		}
   152  
   153  		const confirmErrorMessage = "You already requested a confirmation link. You may request another confirmation link when 24 hours have passed."
   154  		ipHash := s.hashIP(r)
   155  		ipSub := db.SubscriptionByIP(ipHash)
   156  		if ipSub != nil {
   157  			data.BoardError(w, confirmErrorMessage)
   158  			return
   159  		}
   160  
   161  		var confirmed bool
   162  		subs := db.SubscriptionsByEmail(email)
   163  		for _, sub := range subs {
   164  			if sub.Confirm == 0 {
   165  				confirmed = true
   166  				break
   167  			}
   168  		}
   169  
   170  		var confirmTime int64
   171  		if !confirmed {
   172  			if len(subs) != 0 {
   173  				data.BoardError(w, confirmErrorMessage)
   174  				return
   175  			}
   176  
   177  			if s.notificationsPattern != nil {
   178  				var matched bool
   179  				address := ParseEmail(email)
   180  				if address != "" {
   181  					atSymbol := strings.IndexRune(address, '@')
   182  					if atSymbol != -1 {
   183  						domain := address[atSymbol+1:]
   184  						matched = s.notificationsPattern.MatchString(domain)
   185  					}
   186  				}
   187  				if !matched {
   188  					data.BoardError(w, "Sorry, only the following email address domains are allowed: "+s.config.MailDomains)
   189  					return
   190  				}
   191  			}
   192  
   193  			confirmTime = time.Now().Unix()
   194  		}
   195  
   196  		sub := &Subscription{
   197  			Confirm: confirmTime,
   198  			Email:   email,
   199  		}
   200  		if !confirmed {
   201  			sub.IP = ipHash
   202  		}
   203  		if data.Post != nil {
   204  			sub.Target = data.Post.ID
   205  		} else {
   206  			sub.Board = data.Board.ID
   207  			sub.Target = int(FormRange(r, "notify", SubscriptionThreads, SubscriptionAll))
   208  		}
   209  		err := sub.Validate()
   210  		if err != nil {
   211  			data.BoardError(w, fmt.Sprintf("Failed to add subscription: %s", err))
   212  			return
   213  		}
   214  
   215  		var target string
   216  		if data.Post != nil {
   217  			target = fmt.Sprintf("No.%d", data.Post.ID)
   218  		} else {
   219  			target = data.Board.Path()
   220  		}
   221  		if !confirmed {
   222  			const errorMessage = "Failed to send confirmation link. Please try again later."
   223  			client, err := s.connectToMailServer()
   224  			if err != nil {
   225  				data.BoardError(w, errorMessage)
   226  				return
   227  			}
   228  			subject := gotext.Get("Subscribe to %s", target)
   229  			key := md5Sum(s.hashData(md5Sum(email)))
   230  			confirmKey := s.subscriptionConfirmKey(sub)
   231  			message := s.opt.SiteHome + "sriracha/subscribe/?email=" + email + "&key=" + key + "&confirm=" + confirmKey
   232  			err = s.sendMail(client, sub.Email, subject, message)
   233  			client.Close()
   234  			if err != nil {
   235  				data.BoardError(w, errorMessage)
   236  				return
   237  			}
   238  		}
   239  
   240  		var updated bool
   241  		if confirmed {
   242  			for _, existing := range subs {
   243  				if sub.Board == existing.Board && (sub.Board != 0 || sub.Target == existing.Target) {
   244  					existing.Board = sub.Board
   245  					existing.Target = sub.Target
   246  					db.UpdateSubscription(existing)
   247  					updated = true
   248  					break
   249  				}
   250  			}
   251  		}
   252  		if !updated {
   253  			db.AddSubscription(sub)
   254  		}
   255  
   256  		data.Template = "board_info"
   257  		if !confirmed {
   258  			data.Info = "Please confirm your subscription by clicking the link emailed to you."
   259  		} else {
   260  			data.Info = fmt.Sprintf("Subscribed to %s", target)
   261  		}
   262  	}
   263  
   264  	data.execute(w)
   265  }
   266  

View as plain text