Skip to content

Commit

Permalink
Private forum
Browse files Browse the repository at this point in the history
  • Loading branch information
s-gv committed Oct 3, 2021
1 parent d81a2a9 commit 1ebb72a
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 41 deletions.
83 changes: 52 additions & 31 deletions models/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,35 @@ import (
)

type Domain struct {
DomainID int `db:"domain_id"`
DomainName string `db:"domain_name"`
ForumName string `db:"forum_name"`
NoRegularSignupMsg string `db:"no_regular_signup_msg"`
SignupToken string `db:"signup_token"`
EditWindow int `db:"edit_window"`
AutoTopicCloseDays int `db:"auto_topic_close_days"`
UserActivityWindow int `db:"user_activity_window"`
MaxNumActivity int `db:"max_num_activity"`
HeaderMsg string `db:"header_msg"`
Logo template.URL `db:"logo"`
Icon template.URL `db:"icon"`
SMTPHost string `db:"smtp_host"`
SMTPPort int `db:"smtp_port"`
SMTPUser string `db:"smtp_user"`
SMTPPass string `db:"smtp_pass"`
DefaultFromEmail string `db:"default_from_email"`
IsRegularSignupEnabled bool `db:"is_regular_signup_enabled"`
IsReadOnly bool `db:"is_readonly"`
IsGroupSub bool `db:"enable_group_sub"`
IsTopicAutoSub bool `db:"enable_topic_autosub"`
IsCommentAutoSub bool `db:"enable_comment_autosub"`
ArchivedAt sql.NullTime `db:"archived_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DomainID int `db:"domain_id"`
DomainName string `db:"domain_name"`
ForumName string `db:"forum_name"`
NoRegularSignupMsg string `db:"no_regular_signup_msg"`
WhitelistedEmailDomains string `db:"whitelisted_email_domains"`
SignupToken string `db:"signup_token"`
EditWindow int `db:"edit_window"`
AutoTopicCloseDays int `db:"auto_topic_close_days"`
UserActivityWindow int `db:"user_activity_window"`
MaxNumActivity int `db:"max_num_activity"`
HeaderMsg string `db:"header_msg"`
Logo template.URL `db:"logo"`
Icon template.URL `db:"icon"`
SMTPHost string `db:"smtp_host"`
SMTPPort int `db:"smtp_port"`
SMTPUser string `db:"smtp_user"`
SMTPPass string `db:"smtp_pass"`
DefaultFromEmail string `db:"default_from_email"`
IsPrivate bool `db:"is_private"`
IsRegularSigninEnabled bool `db:"is_regular_signin_enabled"`
IsAutoUserCreationOnEmailSigninEnabled bool `db:"is_auto_user_creation_on_email_signin_enabled"`
IsRegularSignupEnabled bool `db:"is_regular_signup_enabled"`
IsReadOnly bool `db:"is_readonly"`
IsGroupSub bool `db:"enable_group_sub"`
IsTopicAutoSub bool `db:"enable_topic_autosub"`
IsCommentAutoSub bool `db:"enable_comment_autosub"`
ArchivedAt sql.NullTime `db:"archived_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}

func CreateDomain(domainName string) error {
Expand Down Expand Up @@ -85,24 +89,41 @@ func GetDomainIDByName(domainName string) *int {
func UpdateDomainByID(
domainID int,
forumName string,
whitelistedEmailDomains string,
logo string,
icon string,
isRegularSignupEnabled bool,
isRegularSigninEnabled bool,
isAutoUserCreationOnEmailSigninEnabled bool,
isReadOnly bool,
isPrivate bool,
signupToken string,
) {
_, err := DB.Exec(`
UPDATE domains
SET
forum_name = $2,
is_regular_signup_enabled = $3,
is_readonly = $4,
signup_token = $5,
logo = $6,
icon = $7
whitelisted_email_domains = $3,
is_regular_signup_enabled = $4,
is_regular_signin_enabled = $5,
is_auto_user_creation_on_email_signin_enabled = $6,
is_readonly = $7,
is_private = $8,
signup_token = $9,
logo = $10,
icon = $11
WHERE
domain_id = $1;`,
domainID, forumName, isRegularSignupEnabled, isReadOnly, signupToken, logo,
domainID,
forumName,
whitelistedEmailDomains,
isRegularSignupEnabled,
isRegularSigninEnabled,
isAutoUserCreationOnEmailSigninEnabled,
isReadOnly,
isPrivate,
signupToken,
logo,
icon,
)
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion models/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"errors"
)

const CurrentDBVersion = 1
const CurrentDBVersion = 2

func Migrate() error {
iver := GetDBVersion()
Expand All @@ -21,6 +21,9 @@ func Migrate() error {
if iver < 1 {
migrate001(DB)
}
if iver < 2 {
migrate002(DB)
}
return nil
}

Expand Down
17 changes: 17 additions & 0 deletions models/migration002.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2021 Orange Forum authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package models

import "github.com/jmoiron/sqlx"

func migrate002(db *sqlx.DB) {
db.MustExec(`ALTER TABLE domains
ADD COLUMN is_private BOOL NOT NULL DEFAULT false,
ADD COLUMN is_regular_signin_enabled BOOL NOT NULL DEFAULT true,
ADD COLUMN is_auto_user_creation_on_email_signin_enabled BOOL NOT NULL DEFAULT false,
ADD COLUMN whitelisted_email_domains VARCHAR(250) NOT NULL DEFAULT '';
`)
db.MustExec(`UPDATE configs SET val = $1 WHERE name = $2;`, "2", DBVersion)
}
13 changes: 13 additions & 0 deletions templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ <h2>Admin</h2>
<label for="forum_name" class="form-label">Forum Name</label>
<input type="text" class="form-control" name="forum_name" id="forum_name" minlength="3" maxlength="30" placeholder="Display Name" value="{{ .Domain.ForumName }}">
</div>
<div class="form-row">
<label for="whitelisted_email_domains" class="form-label">Whitelisted Email Domains</label>
<input type="text" class="form-control" name="whitelisted_email_domains" id="whitelisted_email_domains" minlength="3" maxlength="30" placeholder="Whitelisted Email Domains" value="{{ .Domain.WhitelistedEmailDomains }}">
</div>
<div class="form-row">
<label for="logo" class="form-label">Logo</label>
<textarea id="logo" class="form-control" name="logo" placeholder="image data-url" rows="12" cols="50">{{ .Domain.Logo }}</textarea>
Expand All @@ -21,12 +25,21 @@ <h2>Admin</h2>
<textarea id="icon" class="form-control" name="icon" placeholder="image data-url" rows="12" cols="50">{{ .Domain.Icon }}</textarea>
</div>
<br>
<input type="checkbox" id="is_auto_user_creation_on_email_signin_enabled" name="is_auto_user_creation_on_email_signin_enabled" value="1" {{ if .Domain.IsAutoUserCreationOnEmailSigninEnabled }}checked{{ end }}>
<label for="is_auto_user_creation_on_email_signin_enabled">Auto create user on email signin</label>
<br>
<input type="checkbox" id="is_regular_signup_enabled" name="is_regular_signup_enabled" value="1" {{ if .Domain.IsRegularSignupEnabled }}checked{{ end }}>
<label for="is_regular_signup_enabled">Enable public signup</label>
<br>
<input type="checkbox" id="is_regular_signin_enabled" name="is_regular_signin_enabled" value="1" {{ if .Domain.IsRegularSigninEnabled }}checked{{ end }}>
<label for="is_regular_signin_enabled">Enable regular / password signin</label>
<br><br>
<input type="checkbox" id="is_readonly" name="is_readonly" value="1" {{ if .Domain.IsReadOnly }}checked{{ end }}>
<label for="is_readonly">Enable read-only mode</label>
<br>
<input type="checkbox" id="is_private" name="is_private" value="1" {{ if .Domain.IsPrivate }}checked{{ end }}>
<label for="is_private">Enable private mode</label>
<br>
{{ if not .Domain.IsRegularSignupEnabled }}
<div class="form-row">
<label for="signup_token" class="form-label">Signup Token</label>
Expand Down
17 changes: 16 additions & 1 deletion views/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ func adminHandler(w http.ResponseWriter, r *http.Request) {
}

forumName := r.PostFormValue("forum_name")
whitelistedEmailDomains := r.PostFormValue("whitelisted_email_domains")
logo := r.PostFormValue("logo")
icon := r.PostFormValue("icon")
isRegularSignupEnabled := r.PostFormValue("is_regular_signup_enabled") == "1"
isRegularSigninEnabled := r.PostFormValue("is_regular_signin_enabled") == "1"
isAutoUserCreationOnEmailSigninEnabled := r.PostFormValue("is_auto_user_creation_on_email_signin_enabled") == "1"
isReadOnly := r.PostFormValue("is_readonly") == "1"
isPrivate := r.PostFormValue("is_private") == "1"
signupToken := r.PostFormValue("signup_token")

if len(forumName) < 3 || len(forumName) > 30 {
Expand All @@ -55,7 +59,18 @@ func adminHandler(w http.ResponseWriter, r *http.Request) {
}

if errMsg == "" {
models.UpdateDomainByID(domain.DomainID, forumName, logo, icon, isRegularSignupEnabled, isReadOnly, signupToken)
models.UpdateDomainByID(domain.DomainID,
forumName,
whitelistedEmailDomains,
logo,
icon,
isRegularSignupEnabled,
isRegularSigninEnabled,
isAutoUserCreationOnEmailSigninEnabled,
isReadOnly,
isPrivate,
signupToken,
)
http.Redirect(w, r, basePath+"admin", http.StatusSeeOther)
return
}
Expand Down
62 changes: 55 additions & 7 deletions views/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package views
import (
"context"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"github.com/golang/glog"
"github.com/google/uuid"
"github.com/gorilla/csrf"
"github.com/lestrrat-go/jwx/jwt"
"github.com/s-gv/orangeforum/models"
Expand All @@ -27,6 +29,17 @@ var userNameReg *regexp.Regexp
var nextURLReg *regexp.Regexp
var emailReg *regexp.Regexp

func getNextPathFromURL(url *url.URL) string {
nextPath := url.Path
if nextPath != "" && nextPath[len(nextPath)-1] != '/' {
nextPath = nextPath + "/"
}
if url.RawQuery != "" {
nextPath = nextPath + "?" + url.RawQuery
}
return nextPath
}

func cleanNextURL(next string, basePath string) string {
if next == "" || next[0] != '/' {
return basePath
Expand All @@ -44,6 +57,9 @@ func authenticate(id int, basePath string, w http.ResponseWriter) error {
if basePath != "" {
path = basePath
}
if path != "/" && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
if err == nil {
cookie := http.Cookie{
Name: "jwt",
Expand All @@ -59,7 +75,7 @@ func authenticate(id int, basePath string, w http.ResponseWriter) error {

func mustAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
domain := r.Context().Value(ctxDomain).(*models.Domain)
token, claims, err := jwtauth.FromContext(r.Context())
basePath, _ := r.Context().Value(ctxBasePath).(string)

Expand All @@ -68,7 +84,7 @@ func mustAuth(next http.Handler) http.Handler {
if iat, ok := claims["iat"].(time.Time); ok {
userID, _ := strconv.Atoi(uid)
user := models.GetUserByID(userID)
if user != nil && user.LogoutAt.Before(iat) && user.DomainID == domainID && !user.BannedAt.Valid {
if user != nil && user.LogoutAt.Before(iat) && user.DomainID == domain.DomainID && !user.BannedAt.Valid {
ctx := context.WithValue(r.Context(), CtxUserKey, user)
// Token is authenticated, pass it through
next.ServeHTTP(w, r.WithContext(ctx))
Expand All @@ -86,7 +102,7 @@ func mustAuth(next http.Handler) http.Handler {
HttpOnly: true,
})
if r.Method == "GET" {
http.Redirect(w, r, basePath+"auth/signin?next="+r.URL.Path+"?"+r.URL.RawQuery, http.StatusSeeOther)
http.Redirect(w, r, basePath+"auth/signin?next="+getNextPathFromURL(r.URL), http.StatusSeeOther)
} else {
http.Redirect(w, r, basePath, http.StatusSeeOther)
}
Expand All @@ -95,15 +111,16 @@ func mustAuth(next http.Handler) http.Handler {

func canAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
domain := r.Context().Value(ctxDomain).(*models.Domain)
token, claims, err := jwtauth.FromContext(r.Context())
basePath, _ := r.Context().Value(ctxBasePath).(string)

if err == nil && token != nil && jwt.Validate(token) == nil {
if uid, ok := claims["user_id"].(string); ok {
if iat, ok := claims["iat"].(time.Time); ok {
userID, _ := strconv.Atoi(uid)
user := models.GetUserByID(userID)
if user != nil && user.LogoutAt.Before(iat) && user.DomainID == domainID {
if user != nil && user.LogoutAt.Before(iat) && user.DomainID == domain.DomainID {
ctx := context.WithValue(r.Context(), CtxUserKey, user)
// Token is authenticated, pass it through
next.ServeHTTP(w, r.WithContext(ctx))
Expand All @@ -113,6 +130,22 @@ func canAuth(next http.Handler) http.Handler {
}
}

if domain.IsPrivate {
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: "",
Path: "/",
Expires: time.Now().Add(-300 * time.Hour),
HttpOnly: true,
})
if r.Method == "GET" {
http.Redirect(w, r, basePath+"auth/signin?next="+getNextPathFromURL(r.URL), http.StatusSeeOther)
} else {
http.Redirect(w, r, basePath, http.StatusSeeOther)
}
return
}

next.ServeHTTP(w, r)
})
}
Expand Down Expand Up @@ -173,6 +206,16 @@ func postAuthOneTimeSignIn(w http.ResponseWriter, r *http.Request) {
errMsg := "E-mail not found"

user := models.GetUserByEmail(domain.DomainID, email)

if user == nil && domain.IsAutoUserCreationOnEmailSigninEnabled {
if emailErrMsg := validateEmail(email, domain); emailErrMsg != "" {
errMsg = emailErrMsg
} else {
displayName := strings.Split(email, "@")[0]
models.CreateUser(domain.DomainID, email, displayName, uuid.NewString())
user = models.GetUserByEmail(domain.DomainID, email)
}
}
if user != nil {
errMsg = "A one time sign-in link has been sent to your email"
token := models.UpdateUserOneTimeLoginTokenByID(user.UserID)
Expand Down Expand Up @@ -234,7 +277,7 @@ func getAuthSignUp(w http.ResponseWriter, r *http.Request) {
})
}

func validateEmail(email string) string {
func validateEmail(email string, domain *models.Domain) string {
errMsg := ""
if !strings.Contains(email, "@") {
errMsg = "Invalid email"
Expand All @@ -244,6 +287,11 @@ func validateEmail(email string) string {
} else if emailReg.ReplaceAllString(email, "") != email {
errMsg = "Email should not have non-alphanumeric characters"
}
if domain.WhitelistedEmailDomains != "" {
if !strings.Contains(domain.WhitelistedEmailDomains, email[strings.Index(email, "@")+1:]) {
errMsg = "Email does not belong to a whitelisted domain"
}
}
return errMsg
}

Expand All @@ -266,7 +314,7 @@ func postAuthSignUp(w http.ResponseWriter, r *http.Request) {

errMsg := ""

errMsg = validateEmail(email)
errMsg = validateEmail(email, domain)

if len(passwd) < 6 {
errMsg = "Password should have at least 6 characters"
Expand Down
2 changes: 1 addition & 1 deletion views/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func profileHandler(w http.ResponseWriter, r *http.Request) {
newIsBanned = false
}

errMsg = validateEmail(newEmail)
errMsg = validateEmail(newEmail, domain)

if len(newDisplayName) < 3 || len(newDisplayName) > 30 {
errMsg = "Display name should have between 3 and 30 characters"
Expand Down

0 comments on commit 1ebb72a

Please sign in to comment.