1
mirror of https://gitlab.com/commento/commento.git synced 2025-06-29 22:56:37 -04:00

frontend, api: allow custom emails as commenters

I'm really sorry this came out as one huge commit, but I'm afraid
I can't split this up.

Closes https://gitlab.com/commento/commento-ce/issues/9
This commit is contained in:
Adhityaa
2018-06-10 22:45:56 +05:30
parent e42f77b4eb
commit 2020405e8b
25 changed files with 837 additions and 267 deletions

View File

@ -8,7 +8,7 @@ import (
func TestCommentApproveBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
commentHex, _ := commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())

View File

@ -151,5 +151,6 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
"requireIdentification": d.RequireIdentification,
"isFrozen": d.State == "frozen",
"isModerator": isModerator,
"configuredOauths": configuredOauths,
})
}

View File

@ -9,7 +9,7 @@ import (
func TestCommentListBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
commentNew(commenterHex, "example.com", "/path.html", "root", "**bar**", "approved", time.Now().UTC())
@ -65,7 +65,7 @@ func TestCommentListEmpty(t *testing.T) {
func TestCommentListSelfUnapproved(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())

View File

@ -116,5 +116,5 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
return
}
writeBody(w, response{"success": true, "commentHex": commentHex})
writeBody(w, response{"success": true, "commentHex": commentHex, "approved": state == "approved"})
}

View File

@ -8,9 +8,9 @@ import (
func TestCommentVoteBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google")
cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google")
cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google")
cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google", "")
cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google", "")
cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google", "")
c0, _ := commentNew(cr0, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())

View File

@ -7,7 +7,7 @@ import (
func TestCommenterGetByHexBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
c, err := commenterGetByHex(commenterHex)
if err != nil {
@ -33,7 +33,7 @@ func TestCommenterGetByHexEmpty(t *testing.T) {
func TestCommenterGetBySession(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew()
@ -63,7 +63,7 @@ func TestCommenterGetBySessionEmpty(t *testing.T) {
func TestCommenterGetByName(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew()

71
api/commenter_login.go Normal file
View File

@ -0,0 +1,71 @@
package main
import (
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
func commenterLogin(email string, password string) (string, error) {
if email == "" || password == "" {
return "", errorMissingField
}
statement := `
SELECT commenterHex, passwordHash
FROM commenters
WHERE email = $1 AND provider = 'commento';
`
row := db.QueryRow(statement, email)
var commenterHex string
var passwordHash string
if err := row.Scan(&commenterHex, &passwordHash); err != nil {
return "", errorInvalidEmailPassword
}
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)); err != nil {
// TODO: is this the only possible error?
return "", errorInvalidEmailPassword
}
session, err := randomHex(32)
if err != nil {
logger.Errorf("cannot create session hex: %v", err)
return "", errorInternal
}
statement = `
INSERT INTO
commenterSessions (session, commenterHex, creationDate)
VALUES ($1, $2, $3 );
`
_, err = db.Exec(statement, session, commenterHex, time.Now().UTC())
if err != nil {
logger.Errorf("cannot insert session token: %v\n", err)
return "", errorInternal
}
return session, nil
}
func commenterLoginHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Email *string `json:"email"`
Password *string `json:"password"`
}
var x request
if err := unmarshalBody(r, &x); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
session, err := commenterLogin(*x.Email, *x.Password)
if err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
writeBody(w, response{"success": true, "session": session})
}

View File

@ -0,0 +1,58 @@
package main
import (
"testing"
)
func TestCommenterLoginBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
t.Errorf("expected error not found when logging in without creating an account")
return
}
commenterNew("test@example.com", "Test", "undefined", "undefined", "commento", "hunter2")
if _, err := commenterLogin("test@example.com", "hunter2"); err != nil {
t.Errorf("unexpected error when logging in: %v", err)
return
}
if _, err := commenterLogin("test@example.com", "h******"); err == nil {
t.Errorf("expected error not found when given wrong password")
return
}
if session, err := commenterLogin("test@example.com", "hunter2"); session == "" {
t.Errorf("empty session on successful login: %v", err)
return
}
}
func TestCommenterLoginEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterLogin("test@example.com", ""); err == nil {
t.Errorf("expected error not found when passing empty password")
return
}
commenterNew("test@example.com", "Test", "undefined", "", "commenter", "hunter2")
if _, err := commenterLogin("test@example.com", ""); err == nil {
t.Errorf("expected error not found when passing empty password")
return
}
}
func TestCommenterLoginNonCommento(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterNew("test@example.com", "Test", "undefined", "undefined", "google", "")
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
t.Errorf("expected error not found logging into a non-Commento account")
return
}
}

View File

@ -1,28 +1,74 @@
package main
import (
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
func commenterNew(email string, name string, link string, photo string, provider string) (string, error) {
func commenterNew(email string, name string, link string, photo string, provider string, password string) (string, error) {
if email == "" || name == "" || link == "" || photo == "" || provider == "" {
return "", errorMissingField
}
if provider == "commento" && password == "" {
return "", errorMissingField
}
if _, err := commenterGetByEmail(provider, email); err == nil {
return "", errorEmailAlreadyExists
}
commenterHex, err := randomHex(32)
if err != nil {
return "", errorInternal
}
passwordHash := []byte{}
if (password != "") {
passwordHash, err = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logger.Errorf("cannot generate hash from password: %v\n", err)
return "", errorInternal
}
}
statement := `
INSERT INTO
commenters (commenterHex, email, name, link, photo, provider, joinDate)
VALUES ($1, $2, $3, $4, $5, $6, $7 );
commenters (commenterHex, email, name, link, photo, provider, passwordHash, joinDate)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8 );
`
_, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, time.Now().UTC())
_, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, string(passwordHash), time.Now().UTC())
if err != nil {
logger.Errorf("cannot insert commenter: %v", err)
return "", errorInternal
}
return commenterHex, nil
}
func commenterNewHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Email *string `json:"email"`
Name *string `json:"name"`
Website *string `json:"website"`
Password *string `json:"password"`
}
var x request
if err := unmarshalBody(r, &x); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
// TODO: add gravatar?
// TODO: email confirmation if provider = commento?
// TODO: email confirmation if provider = commento?
if _, err := commenterNew(*x.Email, *x.Name, *x.Website, "undefined", "commento", *x.Password); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
writeBody(w, response{"success": true, "confirmEmail": smtpConfigured})
}

View File

@ -7,7 +7,7 @@ import (
func TestCommenterNewBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google"); err != nil {
if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err != nil {
t.Errorf("unexpected error creating new commenter: %v", err)
return
}
@ -16,13 +16,22 @@ func TestCommenterNewBasics(t *testing.T) {
func TestCommenterNewEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google"); err == nil {
if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err == nil {
t.Errorf("expected error not found creating new commenter with empty email")
return
}
if _, err := commenterNew("", "", "", "", ""); err == nil {
if _, err := commenterNew("", "", "", "", "", ""); err == nil {
t.Errorf("expected error not found creating new commenter with empty everything")
return
}
}
func TestCommenterNewCommento(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterNew("test@example.com", "Test", "undefined", "", "commento", ""); err == nil {
t.Errorf("expected error not found creating new commento account with empty password")
return
}
}

View File

@ -7,7 +7,7 @@ import (
func TestCommenterSessionGetBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew()

View File

@ -29,8 +29,8 @@ func parseConfig() error {
"SMTP_PORT": "",
"SMTP_FROM_ADDRESS": "",
"OAUTH_GOOGLE_KEY": "",
"OAUTH_GOOGLE_SECRET": "",
"GOOGLE_KEY": "",
"GOOGLE_SECRET": "",
}
for key, value := range defaults {

View File

@ -2,7 +2,11 @@ package main
import ()
var configuredOauths []string
func oauthConfigure() error {
configuredOauths = []string{}
if err := googleOauthConfigure(); err != nil {
return err
}

View File

@ -37,5 +37,7 @@ func googleOauthConfigure() error {
Endpoint: google.Endpoint,
}
configuredOauths = append(configuredOauths, "google");
return nil
}

View File

@ -64,7 +64,7 @@ func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
link = "undefined"
}
commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google")
commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google", "")
if err != nil {
fmt.Fprintf(w, "Error: %s", err.Error())
return

View File

@ -21,6 +21,8 @@ func initAPIRouter(router *mux.Router) error {
router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST")
router.HandleFunc("/api/commenter/session/new", commenterSessionNewHandler).Methods("GET")
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
router.HandleFunc("/api/commenter/self", commenterSelfHandler).Methods("POST")
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")